mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
sacrifice code quality (temp) for ux
This commit is contained in:
parent
8bfa72fb58
commit
30f031fe1c
48
README.md
48
README.md
@ -46,15 +46,33 @@ go install ./cmd/...
|
||||
|
||||
## Usage
|
||||
|
||||
**WARNING:** The dsfx project is still in development and should not be used in a
|
||||
production environment. The following instructions are for testing and development
|
||||
purposes only. The system implements it's own cryptography and has not been audited
|
||||
by a third party.
|
||||
|
||||
Currently, the target audience consists of developers, testers, and security
|
||||
researchers who are interested in secure file exchange systems. We also welcome
|
||||
homelab enthusiasts, but do **not recommened** using this software as your sole
|
||||
method of secure backup.
|
||||
|
||||
### Starting the Server
|
||||
|
||||
The dsfxnode requires a listening host, port, and an identity key (ED25519 private key in Base64 format). For example:
|
||||
The dsfxnode requires a listening host and port. On it's first run, it will
|
||||
attempt to initialize a new folder at the directory specified by the `-dir` flag.
|
||||
The default value of this is `$HOME/.dsfxnode`. The server will then generate a
|
||||
ED25519 key pair, and begin listening for incoming connections. Your
|
||||
private key is stored unencrypted at `<-dir>/key`. Please ensure that
|
||||
this file is kept secure. Currently the worst case scenario if this file is lost
|
||||
is that you will need to generate a new key pair, and existing connections will
|
||||
not recognize your server anymore. You will still have access to all of your data
|
||||
once the new key is generated and the server is restarted.
|
||||
|
||||
```sh
|
||||
dsfxnode -host localhost -port 8000 -key /path/to/serverkey
|
||||
dsfxnode -host localhost -port 8000
|
||||
```
|
||||
|
||||
> Note, if you need to generate a new ED25519 key, you can use the following command: `go run ./tool/genkey > path/to/masterkey`
|
||||
> Note, if you need to generate a new ED25519 key, you can use the following command: `go run ./tool/genkey > path/to/key`
|
||||
|
||||
Command-line flags for dsfx-server:
|
||||
|
||||
@ -64,8 +82,12 @@ The host interface on which the server will listen.
|
||||
-port (default 8000)
|
||||
The TCP port on which the server will accept connections.
|
||||
|
||||
-key (required)
|
||||
File path to the Base64-encoded ED25519 private key that serves as the server’s master key.
|
||||
-dir (default "~/.dsfxnode")
|
||||
The directory where the server will store files. The default is `$HOME/.dsfxnode`.
|
||||
|
||||
-log (default "<-dir>/log")
|
||||
The file path where the server will write logs. As a special case, you may run
|
||||
`-log stdout` to write logs to standard output.
|
||||
|
||||
Once started, the server will bind to the specified host and port and wait for incoming secure file exchange (or other test) connections. When a client connects, the initial payload (up to 1024 bytes) from the client is read and logged.
|
||||
|
||||
@ -76,13 +98,15 @@ The dsfxctl uses a private key for the client (also an ED25519 key in Base64 for
|
||||
Client command usage:
|
||||
|
||||
```sh
|
||||
dsfxctl -key /path/to/clientkey test <remote_addr>
|
||||
dsfxctl test <remote_addr>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
-key (required)
|
||||
Specifies the file path to your client’s PEM-encoded private key.
|
||||
Command-line flags for dsfx-server:
|
||||
|
||||
-dir (default "~/.dsfxctl")
|
||||
The directory where the client will store files. The default is `$HOME/.dsfxctl`.
|
||||
|
||||
The command-line arguments for the dsfx-client are as follows:
|
||||
|
||||
@ -91,16 +115,20 @@ Tests the connection against the remote dsfx-server instance.
|
||||
|
||||
<remote_addr>:
|
||||
The address of the server in the format “dsfx://IP:PORT#PUBLIC_KEY_BASE_64”.
|
||||
For example, `dsfx://127.0.0.1:8000#<base64 pubkey>” or “dsfx://127.0.0.1:8000#eyJuIjoiLy8v...`
|
||||
For example, `dsfx://127.0.0.1:8000#m8I9H6qf2RLMhwnSHjJAkxq2Zeuv6a+/JDdJB9C6O24=`.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
dsfxctl -key ./dsfx-client/masterkey test dsfx://127.0.0.1:8000#eyJuIjoiLy8v..
|
||||
dsfxctl test dsfx://127.0.0.1:8000#m8I9H6qf2RLMhwnSHjJAkxq2Zeuv6a+/JDdJB9C6O24=
|
||||
```
|
||||
|
||||
If no command or an unrecognized command is provided, the client will print a brief usage message and exit.
|
||||
|
||||
The first time you run the client, it will generate a new ED25519 key pair and
|
||||
store it in the file `<-dir>/key`. This key pair is used for all subsequent
|
||||
connections to the server.
|
||||
|
||||
### Help and Usage Information
|
||||
|
||||
For quick help, simply pass the -h flag:
|
||||
|
@ -3,11 +3,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||
@ -33,9 +36,55 @@ func main() {
|
||||
slog.SetDefault(logger)
|
||||
ctx = logging.WithContext(ctx, logger)
|
||||
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to get home directory", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
flagDir := flag.String("dir", filepath.Join(homedir, ".dsfxctl"), "data directory")
|
||||
|
||||
err = os.Mkdir(*flagDir, 0777)
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
// If the directory already exists, we can ignore the error.
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create directory", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
keyFile, err := os.Open(filepath.Join(*flagDir, "key"))
|
||||
if err != nil {
|
||||
logger.WarnContext(ctx, "key file is missing, reinitializing")
|
||||
logger.WarnContext(ctx, "if this is your first time running dsfxctl, you can ignore this")
|
||||
}
|
||||
if keyFile == nil {
|
||||
logger.InfoContext(ctx, "generating new key")
|
||||
keyFile, err = os.Create(filepath.Join(*flagDir, "key"))
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create key file", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
privkey, err := identity.Generate()
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to generate key", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = keyFile.Write([]byte(base64.StdEncoding.EncodeToString(privkey)))
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to write key", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
defer keyFile.Close()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Commands
|
||||
|
||||
flag.Parse()
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: dsfxctl [command] [args]\n")
|
||||
fmt.Fprintf(os.Stderr, "Commands:\n")
|
||||
@ -44,16 +93,7 @@ func main() {
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flagKey := flag.String("key", "", "the path to the key file")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagKey == "" {
|
||||
logger.ErrorContext(ctx, "private key path is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
id, err := identity.LoadSigningKeyFromFile(*flagKey)
|
||||
id, err := identity.LoadSigningKeyFromFile(filepath.Join(*flagDir, "key"))
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to load private key", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
|
@ -2,34 +2,77 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/network"
|
||||
)
|
||||
|
||||
var (
|
||||
flagHost = flag.String("host", "localhost", "the host to listen on")
|
||||
flagPort = flag.Int("port", 8000, "the port to listen on")
|
||||
flagKey = flag.String("key", "", "the path to the key file")
|
||||
)
|
||||
var ()
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Logger
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to get home directory\n")
|
||||
logUserMsg("error: are you on a real computer?\n")
|
||||
// Look up why this function might fail...
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
flagHost := flag.String("host", "localhost", "the host to listen on")
|
||||
flagPort := flag.Int("port", 8000, "the port to listen on")
|
||||
flagDir := flag.String("dir", filepath.Join(homedir, ".dsfxnode"), "the directory to store data in")
|
||||
flagLogFile := flag.String("log", "logs", "the file to write logs to, relative to -dir")
|
||||
flag.Parse()
|
||||
|
||||
dataDir := *flagDir
|
||||
|
||||
err = os.Mkdir(dataDir, 0777)
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
// If the directory already exists, we can ignore the error.
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to create the data directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var logFile *os.File
|
||||
if *flagLogFile == "stdout" {
|
||||
logFile = os.Stdout
|
||||
} else {
|
||||
logFilePath := filepath.Join(dataDir, *flagLogFile)
|
||||
logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
logUserMsg("warn: log file missing, making a new one: %s\n", logFilePath)
|
||||
logFile, err = os.Create(logFilePath)
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to create log file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer logFile.Close()
|
||||
|
||||
opts := &slog.HandlerOptions{
|
||||
AddSource: false,
|
||||
Level: slog.LevelDebug,
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))
|
||||
logger := slog.New(slog.NewTextHandler(logFile, opts))
|
||||
|
||||
// Everything in the application will attempt to use the logger in stored in
|
||||
// the context, but we also set the default with slog as a fallback. In cases
|
||||
@ -38,26 +81,71 @@ func main() {
|
||||
slog.SetDefault(logger)
|
||||
ctx = logging.WithContext(ctx, logger)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flags
|
||||
keyFile, err := os.Open(filepath.Join(dataDir, "key"))
|
||||
if err != nil {
|
||||
logUserMsg("warn: key file is missing, making a new one\n")
|
||||
logUserMsg("warn: if this is your first time running dsfxctl, you can ignore this\n")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagKey == "" {
|
||||
slog.ErrorContext(ctx, "master key path is required")
|
||||
keyFile, err = os.Create(filepath.Join(dataDir, "key"))
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to create key file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
privkey, err := identity.Generate()
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to generate key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
masterKey, err := identity.LoadSigningKeyFromFile(*flagKey)
|
||||
_, err = keyFile.Write([]byte(base64.StdEncoding.EncodeToString(privkey)))
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to load master key", slog.Any("error", err))
|
||||
logUserMsg("error: failed to write key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
defer keyFile.Close()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flags
|
||||
|
||||
masterKey, err := identity.LoadSigningKeyFromFile(filepath.Join(dataDir, "key"))
|
||||
if err != nil {
|
||||
logUserMsg("error: failed to load key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var admins []string
|
||||
adminsFile, err := os.ReadFile(filepath.Join(dataDir, "admins"))
|
||||
if err != nil {
|
||||
logUserMsg("warn: failed to read admins file.. no one will be able to control this server..\n")
|
||||
logUserMsg(`
|
||||
If you aren't sure what this means, here's a brief explanation:
|
||||
The admins file is a list of public keys that are allowed to control
|
||||
the server. The base are base64 encoded and separated by newlines.
|
||||
The application looks for this file at <dir>/admins, where <dir> is
|
||||
the directory you specified with the -dir flag when starting the
|
||||
system. By default, this is ~/.dsfxnode.
|
||||
|
||||
When you run the dsfxctl command, it will generate a key for you and
|
||||
print it to the console. You can copy this key and paste it into the
|
||||
admins file to give yourself control over the server.
|
||||
`)
|
||||
logUserMsg("\n")
|
||||
}
|
||||
if adminsFile != nil {
|
||||
rawAdmins := strings.Split(string(adminsFile), "\n")
|
||||
for _, admin := range rawAdmins {
|
||||
if admin != "" {
|
||||
admins = append(admins, admin)
|
||||
}
|
||||
}
|
||||
logUserMsg("info: loaded admins: %v\n", admins)
|
||||
}
|
||||
|
||||
tcpAddrRaw := net.JoinHostPort(*flagHost, fmt.Sprint(*flagPort))
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", tcpAddrRaw)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "invalid host or port")
|
||||
logUserMsg("warn: invalid host or port: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
addr := network.FromTCPAddr(tcpAddr, identity.ToPublicKey(masterKey))
|
||||
@ -71,7 +159,11 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, "listener created", slog.String("address", listener.Addr().String()))
|
||||
logUserMsg("info: listener created\n")
|
||||
logUserMsg(">> Operating System: %s\n", runtime.GOOS)
|
||||
logUserMsg(">> Architecture: %s\n", runtime.GOARCH)
|
||||
logUserMsg(">> CPU Cores: %d\n", runtime.NumCPU())
|
||||
logUserMsg(">> Public Address: %s\n", addr.String())
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
@ -100,3 +192,7 @@ func handleConnection(ctx context.Context, conn net.Conn) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func logUserMsg(msg string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000,
|
||||
"dataDirectory": "/home/dustin/.dsfx/data",
|
||||
"masterKeyPath": "/home/dustin/.dsfx/masterkey"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user