From d361d71a2a7df24dbf7bf321d68742b8af5edbd1 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Mon, 4 Oct 2021 16:21:41 -0700 Subject: [PATCH 1/6] adding 'servers' interface to manage multiple servers and support for encrypted configs --- README.md | 2 + app/main.go | 40 ++++------ server.go | 122 +++++++++++++++++++++++++------ serverConfig.go | 119 +++++++++++++++++++++++------- server_tokenboard.go | 8 +- servers.go | 134 ++++++++++++++++++++++++++++++++++ storage/message_store.go | 2 +- storage/message_store_test.go | 2 +- 8 files changed, 350 insertions(+), 79 deletions(-) create mode 100644 servers.go diff --git a/README.md b/README.md index d730e68..62f0f2c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ The app takes the following arguments The app takes the following environment variables - CWTCH_HOME: sets the config dir for the app +`env CONFIG_HOME=./conf ./app` + ## Using the Server When run the app will output standard log lines, one of which will contain the `tofubundle` in purple. This is the part you need to capture and import into a Cwtch client app so you can use the server for hosting groups diff --git a/app/main.go b/app/main.go index 8656290..42eed2f 100644 --- a/app/main.go +++ b/app/main.go @@ -2,10 +2,8 @@ package main import ( "crypto/rand" - "cwtch.im/cwtch/model" "encoding/base64" "flag" - "fmt" cwtchserver "git.openprivacy.ca/cwtch.im/server" "git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/openprivacy/connectivity/tor" @@ -19,10 +17,6 @@ import ( "time" ) -const ( - serverConfigFile = "serverConfig.json" -) - func main() { flagDebug := flag.Bool("debug", false, "Enable debug logging") flagExportTofu := flag.Bool("exportTofuBundle", false, "Export the tofubundle to a file called tofubundle") @@ -52,19 +46,25 @@ func main() { ReportingGroupID: "", ReportingServerAddr: "", } - config.Save(".", "serverConfig.json") + config.ConfigDir = "." + config.FilePath = cwtchserver.ServerConfigFile + config.Encrypted = false + config.Save() return } - serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile) - + serverConfig, err := cwtchserver.LoadCreateDefaultConfigFile(configDir, cwtchserver.ServerConfigFile, false, "") + if err != nil { + log.Errorf("Could not load/create config file: %s\n", err) + return + } // we don't need real randomness for the port, just to avoid a possible conflict... mrand.Seed(int64(time.Now().Nanosecond())) controlPort := mrand.Intn(1000) + 9052 // generate a random password key := make([]byte, 64) - _, err := rand.Read(key) + _, err = rand.Read(key) if err != nil { panic(err) } @@ -79,25 +79,15 @@ func main() { } defer acn.Close() - server := new(cwtchserver.Server) + server := cwtchserver.NewServer(serverConfig) log.Infoln("starting cwtch server...") + log.Infof("Server 'hash name': %s\n", server.HashName()) - server.Setup(serverConfig) - - // TODO create a random group for testing - group, _ := model.NewGroup(tor.GetTorV3Hostname(serverConfig.PublicKey)) - invite, err := group.Invite() - if err != nil { - panic(err) - } - - bundle := server.KeyBundle().Serialize() - tofubundle := fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite) - log.Infof("Server Tofu Bundle (import into client to use server): %s", log.Magenta(tofubundle)) - log.Infof("Server Config: server address:%s", base64.StdEncoding.EncodeToString(bundle)) + log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.Server())) if *flagExportTofu { - ioutil.WriteFile(path.Join(serverConfig.ConfigDir, "tofubundle"), []byte(tofubundle), 0600) + // Todo: change all to server export + ioutil.WriteFile(path.Join(serverConfig.ConfigDir, "tofubundle"), []byte(server.TofuBundle()), 0600) } // Graceful Shutdown diff --git a/server.go b/server.go index e874510..8755327 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,9 @@ package server import ( "crypto/ed25519" "cwtch.im/cwtch/model" + "encoding/base64" + "encoding/binary" + "errors" "fmt" "git.openprivacy.ca/cwtch.im/server/metrics" "git.openprivacy.ca/cwtch.im/server/storage" @@ -15,14 +18,37 @@ import ( "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" + "os" "path" + "strings" "sync" ) +const ( + // ServerConfigFile is the standard filename for a server's config to be written to in a directory + ServerConfigFile = "serverConfig.json" +) + // Server encapsulates a complete, compliant Cwtch server. -type Server struct { +type Server interface { + Identity() primitives.Identity + Run(acn connectivity.ACN) error + KeyBundle() *model.KeyBundle + CheckStatus() (bool, error) + Shutdown() + GetStatistics() Statistics + ConfigureAutostart(autostart bool) + Close() + Delete(password string) error + Onion() string + Server() string + TofuBundle() string + HashName() string +} + +type server struct { service tapir.Service - config Config + config *Config metricsPack metrics.Monitors tokenTapirService tapir.Service tokenServer *privacypass.TokenServer @@ -35,31 +61,39 @@ type Server struct { lock sync.RWMutex } -// Setup initialized a server from a given configuration -func (s *Server) Setup(serverConfig Config) { - s.config = serverConfig +// NewServer creates and configures a new server based on the supplied configuration +func NewServer(serverConfig *Config) Server { + server := new(server) + server.running = false + server.config = serverConfig bs := new(persistence.BoltPersistence) bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db")) - s.tokenServer = privacypass.NewTokenServerFromStore(&serverConfig.TokenServiceK, bs) - log.Infof("Y: %v", s.tokenServer.Y) - s.tokenService = s.config.TokenServiceIdentity() - s.tokenServicePrivKey = s.config.TokenServerPrivateKey + server.tokenServer = privacypass.NewTokenServerFromStore(&serverConfig.TokenServiceK, bs) + log.Infof("Y: %v", server.tokenServer.Y) + server.tokenService = server.config.TokenServiceIdentity() + server.tokenServicePrivKey = server.config.TokenServerPrivateKey + return server } // Identity returns the main onion identity of the server -func (s *Server) Identity() primitives.Identity { +func (s *server) Identity() primitives.Identity { return s.config.Identity() } // Run starts a server with the given privateKey -func (s *Server) Run(acn connectivity.ACN) error { - addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey) +func (s *server) Run(acn connectivity.ACN) error { + s.lock.Lock() + defer s.lock.Unlock() + if s.running { + return nil + } + identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey) var service tapir.Service service = new(tor2.BaseOnionService) service.Init(acn, s.config.PrivateKey, &identity) s.service = service - log.Infof("cwtch server running on cwtch:%s\n", addressIdentity+".onion:") + log.Infof("cwtch server running on cwtch:%s\n", s.Onion()) s.metricsPack.Start(service, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile) ms, err := storage.InitializeSqliteMessageStore(path.Join(s.config.ConfigDir, "cwtch.messages"), s.metricsPack.MessageCounter) @@ -87,14 +121,12 @@ func (s *Server) Run(acn connectivity.ACN) error { s.onionServiceStopped = true }() - s.lock.Lock() s.running = true - s.lock.Unlock() return nil } // KeyBundle provides the signed keybundle of the server -func (s *Server) KeyBundle() *model.KeyBundle { +func (s *server) KeyBundle() *model.KeyBundle { kb := model.NewKeyBundle() identity := s.config.Identity() kb.Keys[model.KeyTypeServerOnion] = model.Key(identity.Hostname()) @@ -105,7 +137,7 @@ func (s *Server) KeyBundle() *model.KeyBundle { } // CheckStatus returns true if the server is running and/or an error if any part of the server needs to be restarted. -func (s *Server) CheckStatus() (bool, error) { +func (s *server) CheckStatus() (bool, error) { s.lock.RLock() defer s.lock.RUnlock() if s.onionServiceStopped == true || s.tokenServiceStopped == true { @@ -115,7 +147,7 @@ func (s *Server) CheckStatus() (bool, error) { } // Shutdown kills the app closing all connections and freeing all goroutines -func (s *Server) Shutdown() { +func (s *server) Shutdown() { s.lock.Lock() defer s.lock.Unlock() s.service.Shutdown() @@ -132,7 +164,7 @@ type Statistics struct { // GetStatistics is a stub method for providing some high level information about // the server operation to bundling applications (e.g. the UI) -func (s *Server) GetStatistics() Statistics { +func (s *server) GetStatistics() Statistics { // TODO Statistics from Metrics is very awkward. Metrics needs an overhaul to make safe total := s.existingMessageCount if s.metricsPack.TotalMessageCounter != nil { @@ -145,16 +177,60 @@ func (s *Server) GetStatistics() Statistics { } // ConfigureAutostart sets whether this server should autostart (in the Cwtch UI/bundling application) -func (s *Server) ConfigureAutostart(autostart bool) { +func (s *server) ConfigureAutostart(autostart bool) { s.config.AutoStart = autostart - s.config.Save(s.config.ConfigDir, s.config.FilePath) + s.config.Save() } // Close shuts down the cwtch server in a safe way. -func (s *Server) Close() { +func (s *server) Close() { log.Infof("Shutting down server") s.lock.Lock() defer s.lock.Unlock() - log.Infof("Closing Token Server Database...") + log.Infof("Closing Token server Database...") s.tokenServer.Close() } + +func (s *server) Delete(password string) error { + s.lock.Lock() + defer s.lock.Unlock() + if s.config.Encrypted && !s.config.CheckPassword(password) { + return errors.New("Cannot delete server, passwords do not match") + } + os.RemoveAll(s.config.ConfigDir) + return nil +} + +func (s *server) Onion() string { + return tor.GetTorV3Hostname(s.config.PublicKey) + ".onion" +} + +func (s *server) Server() string { + bundle := s.KeyBundle().Serialize() + return fmt.Sprintf("server:%s", base64.StdEncoding.EncodeToString(bundle)) +} + +func (s *server) TofuBundle() string { + group, _ := model.NewGroup(tor.GetTorV3Hostname(s.config.PublicKey)) + invite, err := group.Invite() + if err != nil { + panic(err) + } + bundle := s.KeyBundle().Serialize() + return fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite) +} + +// TODO demo implementation only, not nearly enough entropy +// TODO Apache license +// https://github.com/dustinkirkland/petname/blob/master/usr/share/petname/small/names.txt +var namesSmall = []string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "haddock", "hagfish", "halibut", "hamster", "herring", "jackass", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "woodcock"} + +func (s *server) HashName() string { + var bytes []byte = s.config.PublicKey + var words []string + for i := 0; i < 8; i++ { + index := int(binary.BigEndian.Uint32(bytes[i*4:(i+1)*4])) % len(namesSmall) + words = append(words, namesSmall[index]) + } + return strings.Join(words, "-") +} diff --git a/serverConfig.go b/serverConfig.go index b305db3..7b3834b 100644 --- a/serverConfig.go +++ b/serverConfig.go @@ -2,15 +2,22 @@ package server import ( "crypto/rand" + v1 "cwtch.im/cwtch/storage/v1" "encoding/json" "git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/openprivacy/log" "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" "io/ioutil" + "os" "path" ) +const ( + // SaltFile is the standard filename to store an encrypted config's SALT under beside it + SaltFile = "SALT" +) + // Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report type Reporting struct { LogMetricsToFile bool `json:"logMetricsToFile"` @@ -22,7 +29,9 @@ type Reporting struct { type Config struct { ConfigDir string `json:"-"` FilePath string `json:"-"` - MaxBufferLines int `json:"maxBufferLines"` + Encrypted bool `json:"-"` + key [32]byte + MaxBufferLines int `json:"maxBufferLines"` PublicKey ed25519.PublicKey `json:"publicKey"` PrivateKey ed25519.PrivateKey `json:"privateKey"` @@ -46,17 +55,8 @@ func (config *Config) TokenServiceIdentity() primitives.Identity { return primitives.InitializeIdentity("", &config.TokenServerPrivateKey, &config.TokenServerPublicKey) } -// Save dumps the latest version of the config to a json file given by filename -func (config *Config) Save(dir, filename string) { - log.Infof("Saving config to %s\n", path.Join(dir, filename)) - bytes, _ := json.MarshalIndent(config, "", "\t") - ioutil.WriteFile(path.Join(dir, filename), bytes, 0600) -} - -// LoadConfig loads a Config from a json file specified by filename -func LoadConfig(configDir, filename string) Config { - log.Infof("Loading config from %s\n", path.Join(configDir, filename)) - config := Config{} +func initDefaultConfig(configDir, filename string, encrypted bool) Config { + config := Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename} id, pk := primitives.InitializeEphemeralIdentity() tid, tpk := primitives.InitializeEphemeralIdentity() @@ -71,8 +71,6 @@ func LoadConfig(configDir, filename string) Config { ReportingServerAddr: "", } config.AutoStart = false - config.ConfigDir = configDir - config.FilePath = filename k := new(ristretto255.Scalar) b := make([]byte, 64) @@ -83,16 +81,87 @@ func LoadConfig(configDir, filename string) Config { } k.FromUniformBytes(b) config.TokenServiceK = *k - - raw, err := ioutil.ReadFile(path.Join(configDir, filename)) - if err == nil { - err = json.Unmarshal(raw, &config) - - if err != nil { - log.Errorf("reading config: %v", err) - } - } - // Always save (first time generation, new version with new variables populated) - config.Save(configDir, filename) return config } + +// LoadCreateDefaultConfigFile loads a Config from or creates a default config and saves it to a json file specified by filename +// if the encrypted flag is true the config is store encrypted by password +func LoadCreateDefaultConfigFile(configDir, filename string, encrypted bool, password string) (*Config, error) { + if _, err := os.Stat(path.Join(configDir, filename)); os.IsNotExist(err) { + return CreateConfig(configDir, filename, encrypted, password) + } + return LoadConfig(configDir, filename, encrypted, password) +} + +// CreateConfig creates a default config and saves it to a json file specified by filename +// if the encrypted flag is true the config is store encrypted by password +func CreateConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { + os.Mkdir(configDir, 0700) + config := initDefaultConfig(configDir, filename, encrypted) + if encrypted { + key, _, err := v1.InitV1Directory(configDir, password) + if err != nil { + log.Errorf("Could not create server directory: %s", err) + return nil, err + } + config.key = key + } + + config.Save() + return &config, nil +} + +// LoadConfig loads a Config from a json file specified by filename +func LoadConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { + log.Infof("Loading config from %s\n", path.Join(configDir, filename)) + + config := initDefaultConfig(configDir, filename, encrypted) + + raw, err := ioutil.ReadFile(path.Join(configDir, filename)) + if err != nil { + return nil, err + } + + if encrypted { + salt, err := ioutil.ReadFile(path.Join(configDir, SaltFile)) + if err != nil { + return nil, err + } + key := v1.CreateKey(password, salt) + settingsStore := v1.NewFileStore(configDir, ServerConfigFile, key) + raw, err = settingsStore.Read() + if err != nil { + return nil, err + } + } + + if err = json.Unmarshal(raw, &config); err != nil { + log.Errorf("reading config: %v", err) + return nil, err + } + + // Always save (first time generation, new version with new variables populated) + config.Save() + return &config, nil +} + +// Save dumps the latest version of the config to a json file given by filename +func (config *Config) Save() error { + log.Infof("Saving config to %s\n", path.Join(config.ConfigDir, config.FilePath)) + bytes, _ := json.MarshalIndent(config, "", "\t") + if config.Encrypted { + settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) + return settingStore.Write(bytes) + } + return ioutil.WriteFile(path.Join(config.ConfigDir, config.FilePath), bytes, 0600) +} + +// CheckPassword returns true if the given password produces the same key as the current stored key, otherwise false. +func (config *Config) CheckPassword(checkpass string) bool { + salt, err := ioutil.ReadFile(path.Join(config.ConfigDir, SaltFile)) + if err != nil { + return false + } + oldkey := v1.CreateKey(checkpass, salt[:]) + return oldkey == config.key +} diff --git a/server_tokenboard.go b/server_tokenboard.go index 0ad36c1..bf5924c 100644 --- a/server_tokenboard.go +++ b/server_tokenboard.go @@ -50,14 +50,14 @@ func (ta *TokenboardServer) Listen() { for { data := ta.connection.Expect() if len(data) == 0 { - log.Debugf("Server Closing Connection") + log.Debugf("server Closing Connection") ta.connection.Close() return // connection is closed } var message groups.Message if err := json.Unmarshal(data, &message); err != nil { - log.Debugf("Server Closing Connection Because of Malformed Client Packet %v", err) + log.Debugf("server Closing Connection Because of Malformed Client Packet %v", err) ta.connection.Close() return // connection is closed } @@ -69,7 +69,7 @@ func (ta *TokenboardServer) Listen() { log.Debugf("Received a Post Message Request: %v", ta.connection.Hostname()) ta.postMessageRequest(postrequest) } else { - log.Debugf("Server Closing Connection Because of PostRequestMessage Client Packet") + log.Debugf("server Closing Connection Because of PostRequestMessage Client Packet") ta.connection.Close() return // connection is closed } @@ -97,7 +97,7 @@ func (ta *TokenboardServer) Listen() { ta.connection.Send(data) } } else { - log.Debugf("Server Closing Connection Because of Malformed ReplayRequestMessage Packet") + log.Debugf("server Closing Connection Because of Malformed ReplayRequestMessage Packet") ta.connection.Close() return // connection is closed } diff --git a/servers.go b/servers.go new file mode 100644 index 0000000..5849245 --- /dev/null +++ b/servers.go @@ -0,0 +1,134 @@ +package server + +import ( + "cwtch.im/cwtch/model" + "errors" + "fmt" + "git.openprivacy.ca/openprivacy/connectivity" + "io/ioutil" + "path" + "sync" +) + +// Servers is an interface to manage multiple Cwtch servers +// Unlike a standalone server, server's dirs will be under one "$CwtchDir/servers" and use a cwtch style localID to obscure +// what servers are hosted. Users are of course free to use a default password. This means Config file will be encrypted +// with cwtch/storage/v1/file_enc and monitor files will not be generated +type Servers interface { + LoadServers(password string) ([]string, error) + CreateServer(password string) (Server, error) + + GetServer(onion string) Server + ListServers() []string + DeleteServer(onion string, currentPassword string) error + + LaunchServers() + ShutdownServer(string) + Shutdown() +} + +type servers struct { + lock sync.Mutex + servers map[string]Server + directory string + acn connectivity.ACN +} + +// NewServers returns a Servers interface to manage a collection of servers +// expecting directory: $CWTCH_HOME/servers +func NewServers(acn connectivity.ACN, directory string) Servers { + return &servers{acn: acn, directory: directory} +} + +// LoadServers will attempt to load any servers in the servers directory that are encrypted with the supplied password +// returns a list of onions identifiers for servers loaded or an error +func (s *servers) LoadServers(password string) ([]string, error) { + s.lock.Lock() + defer s.lock.Unlock() + + dirs, err := ioutil.ReadDir(s.directory) + if err != nil { + return nil, fmt.Errorf("error: cannot read server directory: %v", err) + } + + loadedServers := []string{} + for _, dir := range dirs { + newConfig, err := LoadConfig(path.Join(s.directory, dir.Name()), ServerConfigFile, true, password) + if err == nil { + server := NewServer(newConfig) + s.servers[server.Onion()] = server + loadedServers = append(loadedServers, server.Onion()) + } + } + return loadedServers, nil +} + +// CreateServer creates a new server and stores it, also returns an interface to it +func (s *servers) CreateServer(password string) (Server, error) { + newLocalID := model.GenerateRandomID() + directory := path.Join(s.directory, newLocalID) + config, err := CreateConfig(directory, ServerConfigFile, true, password) + if err != nil { + return nil, err + } + server := NewServer(config) + s.lock.Lock() + defer s.lock.Unlock() + s.servers[server.Onion()] = server + return server, nil +} + +// GetServer returns a server interface for the supplied onion +func (s *servers) GetServer(onion string) Server { + s.lock.Lock() + defer s.lock.Unlock() + return s.servers[onion] +} + +// ListServers returns a list of server onion identifies this servers struct is managing +func (s *servers) ListServers() []string { + s.lock.Lock() + defer s.lock.Unlock() + list := []string{} + for onion := range s.servers { + list = append(list, onion) + } + return list +} + +// DeleteServer delete's the requested server (assuming the passwords match +func (s *servers) DeleteServer(onion string, password string) error { + s.lock.Lock() + defer s.lock.Unlock() + server := s.servers[onion] + if server != nil { + server.Shutdown() + err := server.Delete(password) + delete(s.servers, onion) + return err + } + return errors.New("Server not found") +} + +// LaunchServers Run() all loaded servers +func (s *servers) LaunchServers() { + s.lock.Lock() + defer s.lock.Unlock() + for _, server := range s.servers { + server.Run(s.acn) + } +} + +// ShutdownServer Shutsdown the specified server +func (s *servers) ShutdownServer(onion string) { + s.servers[onion].Shutdown() +} + +// Shutdown shutsdown all the servers +func (s *servers) Shutdown() { + s.lock.Lock() + defer s.lock.Unlock() + for _, server := range s.servers { + server.Shutdown() + } +} diff --git a/storage/message_store.go b/storage/message_store.go index 7cbb274..2e4f83d 100644 --- a/storage/message_store.go +++ b/storage/message_store.go @@ -2,10 +2,10 @@ package storage import ( "cwtch.im/cwtch/protocol/groups" - "git.openprivacy.ca/cwtch.im/server/metrics" "database/sql" "encoding/base64" "fmt" + "git.openprivacy.ca/cwtch.im/server/metrics" "git.openprivacy.ca/openprivacy/log" _ "github.com/mattn/go-sqlite3" // sqlite3 driver ) diff --git a/storage/message_store_test.go b/storage/message_store_test.go index 97316d5..54dd364 100644 --- a/storage/message_store_test.go +++ b/storage/message_store_test.go @@ -2,8 +2,8 @@ package storage import ( "cwtch.im/cwtch/protocol/groups" - "git.openprivacy.ca/cwtch.im/server/metrics" "encoding/binary" + "git.openprivacy.ca/cwtch.im/server/metrics" "git.openprivacy.ca/openprivacy/log" "os" "testing" From ce0b05232ca4fad5478bc227cdf22431cc942afb Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 7 Oct 2021 17:11:18 -0700 Subject: [PATCH 2/6] adjustments --- app/main.go | 2 +- go.mod | 6 ++++-- go.sum | 4 ++++ server.go | 37 ++++++++++++++++++------------------- serverConfig.go | 46 ++++++++++++++++++++++++++++++++++++++++------ servers.go | 10 +++++----- 6 files changed, 72 insertions(+), 33 deletions(-) diff --git a/app/main.go b/app/main.go index 42eed2f..7d28600 100644 --- a/app/main.go +++ b/app/main.go @@ -95,8 +95,8 @@ func main() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c + server.Shutdown() acn.Close() - server.Close() os.Exit(1) }() diff --git a/go.mod b/go.mod index b158b25..8ff9ecf 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,13 @@ go 1.14 require ( cwtch.im/cwtch v0.8.5 - git.openprivacy.ca/cwtch.im/tapir v0.4.2 - git.openprivacy.ca/openprivacy/connectivity v1.4.3 + git.openprivacy.ca/cwtch.im/tapir v0.4.4 + git.openprivacy.ca/openprivacy/connectivity v1.4.5 git.openprivacy.ca/openprivacy/log v1.0.2 github.com/gtank/ristretto255 v0.1.2 github.com/mattn/go-sqlite3 v1.14.7 github.com/struCoder/pidusage v0.2.1 golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee ) + +replace cwtch.im/cwtch => /home/dan/src/go/src/cwtch.im/cwtch diff --git a/go.sum b/go.sum index a08604c..d70cc96 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,14 @@ git.openprivacy.ca/cwtch.im/tapir v0.4.1 h1:9LMpQX41IzecNNlRc1FZKXHg6wlFss679tFs git.openprivacy.ca/cwtch.im/tapir v0.4.1/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.2 h1:bxMWZnVJXX4dqqOFS7ELW4iFkVL4GS8wiRkjRv5rJe8= git.openprivacy.ca/cwtch.im/tapir v0.4.2/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= +git.openprivacy.ca/cwtch.im/tapir v0.4.4 h1:KyuTVmr9GYptTCeR7JDODjmhBBbnIBf9V3NSC4+6bHc= +git.openprivacy.ca/cwtch.im/tapir v0.4.4/go.mod h1:qMFTdmDZITc1BLP1jSW0gVpLmvpg+Zjsh5ek8StwbFE= git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.4.3 h1:i2Ad/U9FlL9dKr2bhRck7lJ8NoWyGtoEfUwoCyMT0fU= git.openprivacy.ca/openprivacy/connectivity v1.4.3/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo= +git.openprivacy.ca/openprivacy/connectivity v1.4.5 h1:UYMdCWPzEAP7LbqdMXGNXmfKjWlvfnKdmewBtnbgQRI= +git.openprivacy.ca/openprivacy/connectivity v1.4.5/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ= git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= diff --git a/server.go b/server.go index 8755327..bd789e5 100644 --- a/server.go +++ b/server.go @@ -37,13 +37,13 @@ type Server interface { CheckStatus() (bool, error) Shutdown() GetStatistics() Statistics - ConfigureAutostart(autostart bool) - Close() Delete(password string) error Onion() string Server() string TofuBundle() string HashName() string + GetAttribute(string) string + SetAttribute(string, string) } type server struct { @@ -122,6 +122,7 @@ func (s *server) Run(acn connectivity.ACN) error { }() s.running = true + s.SetAttribute(AttrEnabled, "true") return nil } @@ -148,13 +149,16 @@ func (s *server) CheckStatus() (bool, error) { // Shutdown kills the app closing all connections and freeing all goroutines func (s *server) Shutdown() { + log.Infof("Shutting down server") s.lock.Lock() defer s.lock.Unlock() s.service.Shutdown() s.tokenTapirService.Shutdown() + log.Infof("Closing Token server Database...") + s.tokenServer.Close() s.metricsPack.Stop() s.running = true - + s.SetAttribute(AttrEnabled, "false") } // Statistics is an encapsulation of information about the server that an operator might want to know at a glance. @@ -176,21 +180,6 @@ func (s *server) GetStatistics() Statistics { } } -// ConfigureAutostart sets whether this server should autostart (in the Cwtch UI/bundling application) -func (s *server) ConfigureAutostart(autostart bool) { - s.config.AutoStart = autostart - s.config.Save() -} - -// Close shuts down the cwtch server in a safe way. -func (s *server) Close() { - log.Infof("Shutting down server") - s.lock.Lock() - defer s.lock.Unlock() - log.Infof("Closing Token server Database...") - s.tokenServer.Close() -} - func (s *server) Delete(password string) error { s.lock.Lock() defer s.lock.Unlock() @@ -202,7 +191,7 @@ func (s *server) Delete(password string) error { } func (s *server) Onion() string { - return tor.GetTorV3Hostname(s.config.PublicKey) + ".onion" + return s.config.Onion() } func (s *server) Server() string { @@ -234,3 +223,13 @@ func (s *server) HashName() string { } return strings.Join(words, "-") } + +// GetAttribute gets a server attribute +func (s *server) GetAttribute(key string) string { + return s.config.GetAttribute(key) +} + +// SetAttribute sets a server attribute +func (s *server) SetAttribute(key, val string) { + s.config.SetAttribute(key, val) +} diff --git a/serverConfig.go b/serverConfig.go index 7b3834b..3f4f5cd 100644 --- a/serverConfig.go +++ b/serverConfig.go @@ -5,17 +5,28 @@ import ( v1 "cwtch.im/cwtch/storage/v1" "encoding/json" "git.openprivacy.ca/cwtch.im/tapir/primitives" + "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" "io/ioutil" "os" "path" + "sync" ) const ( // SaltFile is the standard filename to store an encrypted config's SALT under beside it SaltFile = "SALT" + + // AttrAutostart is the attribute key for autostart setting + AttrAutostart = "autostart" + + // AttrDescription is the attribute key for a user set server description + AttrDescription = "description" + + // AttrEnabled is the attribute key for user toggle of server being enabled + AttrEnabled = "enabled" ) // Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report @@ -42,7 +53,9 @@ type Config struct { TokenServiceK ristretto255.Scalar `json:"tokenServiceK"` ServerReporting Reporting `json:"serverReporting"` - AutoStart bool `json:"autostart"` + + attributes map[string]string + lock sync.Mutex } // Identity returns an encapsulation of the servers keys @@ -55,8 +68,8 @@ func (config *Config) TokenServiceIdentity() primitives.Identity { return primitives.InitializeIdentity("", &config.TokenServerPrivateKey, &config.TokenServerPublicKey) } -func initDefaultConfig(configDir, filename string, encrypted bool) Config { - config := Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename} +func initDefaultConfig(configDir, filename string, encrypted bool) *Config { + config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename} id, pk := primitives.InitializeEphemeralIdentity() tid, tpk := primitives.InitializeEphemeralIdentity() @@ -70,7 +83,8 @@ func initDefaultConfig(configDir, filename string, encrypted bool) Config { ReportingGroupID: "", ReportingServerAddr: "", } - config.AutoStart = false + config.attributes[AttrAutostart] = "false" + config.attributes[AttrEnabled] = "true" k := new(ristretto255.Scalar) b := make([]byte, 64) @@ -108,7 +122,7 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( } config.Save() - return &config, nil + return config, nil } // LoadConfig loads a Config from a json file specified by filename @@ -142,7 +156,7 @@ func LoadConfig(configDir, filename string, encrypted bool, password string) (*C // Always save (first time generation, new version with new variables populated) config.Save() - return &config, nil + return config, nil } // Save dumps the latest version of the config to a json file given by filename @@ -165,3 +179,23 @@ func (config *Config) CheckPassword(checkpass string) bool { oldkey := v1.CreateKey(checkpass, salt[:]) return oldkey == config.key } + +// Onion returns the .onion url for the server +func (config *Config) Onion() string { + return tor.GetTorV3Hostname(config.PublicKey) + ".onion" +} + +// SetAttribute sets a server attribute +func (config *Config) SetAttribute(key, val string) { + config.lock.Lock() + defer config.lock.Unlock() + config.attributes[key] = val + config.Save() +} + +// GetAttribute gets a server attribute +func (config *Config) GetAttribute(key string) string { + config.lock.Lock() + defer config.lock.Unlock() + return config.attributes[key] +} diff --git a/servers.go b/servers.go index 5849245..f8e4f74 100644 --- a/servers.go +++ b/servers.go @@ -22,7 +22,7 @@ type Servers interface { ListServers() []string DeleteServer(onion string, currentPassword string) error - LaunchServers() + LaunchServer(string) ShutdownServer(string) Shutdown() } @@ -54,7 +54,7 @@ func (s *servers) LoadServers(password string) ([]string, error) { loadedServers := []string{} for _, dir := range dirs { newConfig, err := LoadConfig(path.Join(s.directory, dir.Name()), ServerConfigFile, true, password) - if err == nil { + if _, exists := s.servers[newConfig.Onion()]; err == nil && !exists { server := NewServer(newConfig) s.servers[server.Onion()] = server loadedServers = append(loadedServers, server.Onion()) @@ -110,11 +110,11 @@ func (s *servers) DeleteServer(onion string, password string) error { return errors.New("Server not found") } -// LaunchServers Run() all loaded servers -func (s *servers) LaunchServers() { +// LaunchServer Run() the specified server +func (s *servers) LaunchServer(onion string) { s.lock.Lock() defer s.lock.Unlock() - for _, server := range s.servers { + if server, exists := s.servers[onion]; exists { server.Run(s.acn) } } From 206b5782aeb479f8b4103a179cda3dc47c1ea01c Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 29 Oct 2021 16:01:50 -0700 Subject: [PATCH 3/6] refactor to a more usable base for servers/serverConfig --- app/main.go | 4 +-- go.mod | 6 ++--- go.sum | 8 ++++++ server.go | 40 ++++++++++-------------------- serverConfig.go | 65 +++++++++++++++++++++++++++++++++---------------- servers.go | 16 +++++++----- 6 files changed, 80 insertions(+), 59 deletions(-) diff --git a/app/main.go b/app/main.go index 7d28600..45c814e 100644 --- a/app/main.go +++ b/app/main.go @@ -81,9 +81,9 @@ func main() { server := cwtchserver.NewServer(serverConfig) log.Infoln("starting cwtch server...") - log.Infof("Server 'hash name': %s\n", server.HashName()) + log.Infof("Server %s\n", server.Onion()) - log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.Server())) + log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.ServerBundle())) if *flagExportTofu { // Todo: change all to server export diff --git a/go.mod b/go.mod index 8ff9ecf..52cc76b 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.14 require ( cwtch.im/cwtch v0.8.5 - git.openprivacy.ca/cwtch.im/tapir v0.4.4 - git.openprivacy.ca/openprivacy/connectivity v1.4.5 - git.openprivacy.ca/openprivacy/log v1.0.2 + git.openprivacy.ca/cwtch.im/tapir v0.4.9 + git.openprivacy.ca/openprivacy/connectivity v1.5.0 + git.openprivacy.ca/openprivacy/log v1.0.3 github.com/gtank/ristretto255 v0.1.2 github.com/mattn/go-sqlite3 v1.14.7 github.com/struCoder/pidusage v0.2.1 diff --git a/go.sum b/go.sum index d70cc96..de82a84 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cwtch.im/cwtch v0.8.0 h1:QDRaDBTXefFRPZPqUMtxoNhOcgXv0rl0bGjysSOmJX0= cwtch.im/cwtch v0.8.0/go.mod h1:+SY/4ueF1U7mK+CX8hZFbtd+GC1lx/cReo110KgtQAw= cwtch.im/cwtch v0.8.5 h1:W67jAF2oRwqWytbZEv1UeCqW0cU2x69tgUw8iy27xFA= cwtch.im/cwtch v0.8.5/go.mod h1:5GHxaaeVnKeXSU64IvtCKzkqhU8DRiLoVM+tiBT8kkc= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.openprivacy.ca/cwtch.im/tapir v0.4.0 h1:clG8uORt0NKEhT4P+Dpw1pzyUuYzYBMevGqn2pciKk8= git.openprivacy.ca/cwtch.im/tapir v0.4.0/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.1 h1:9LMpQX41IzecNNlRc1FZKXHg6wlFss679tFsa3vzb3Y= @@ -12,15 +14,21 @@ git.openprivacy.ca/cwtch.im/tapir v0.4.2 h1:bxMWZnVJXX4dqqOFS7ELW4iFkVL4GS8wiRkj git.openprivacy.ca/cwtch.im/tapir v0.4.2/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.4 h1:KyuTVmr9GYptTCeR7JDODjmhBBbnIBf9V3NSC4+6bHc= git.openprivacy.ca/cwtch.im/tapir v0.4.4/go.mod h1:qMFTdmDZITc1BLP1jSW0gVpLmvpg+Zjsh5ek8StwbFE= +git.openprivacy.ca/cwtch.im/tapir v0.4.9 h1:LXonlztwvI1F1++0IyomIcDH1/Bxzo+oN8YjGonNvjM= +git.openprivacy.ca/cwtch.im/tapir v0.4.9/go.mod h1:p4bHo3DAO8wwimU6JAeZXbfPQ4jnoA2bV+4YvknWTNQ= git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.4.3 h1:i2Ad/U9FlL9dKr2bhRck7lJ8NoWyGtoEfUwoCyMT0fU= git.openprivacy.ca/openprivacy/connectivity v1.4.3/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo= git.openprivacy.ca/openprivacy/connectivity v1.4.5 h1:UYMdCWPzEAP7LbqdMXGNXmfKjWlvfnKdmewBtnbgQRI= git.openprivacy.ca/openprivacy/connectivity v1.4.5/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ= +git.openprivacy.ca/openprivacy/connectivity v1.5.0 h1:ZxsR/ZaVKXIkD2x6FlajZn62ciNQjamrI4i/5xIpdoQ= +git.openprivacy.ca/openprivacy/connectivity v1.5.0/go.mod h1:UjQiGBnWbotmBzIw59B8H6efwDadjkKzm3RPT1UaIRw= git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= +git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0= +git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/server.go b/server.go index bd789e5..1239b9c 100644 --- a/server.go +++ b/server.go @@ -4,7 +4,6 @@ import ( "crypto/ed25519" "cwtch.im/cwtch/model" "encoding/base64" - "encoding/binary" "errors" "fmt" "git.openprivacy.ca/cwtch.im/server/metrics" @@ -20,7 +19,6 @@ import ( "git.openprivacy.ca/openprivacy/log" "os" "path" - "strings" "sync" ) @@ -39,9 +37,8 @@ type Server interface { GetStatistics() Statistics Delete(password string) error Onion() string - Server() string + ServerBundle() string TofuBundle() string - HashName() string GetAttribute(string) string SetAttribute(string, string) } @@ -152,13 +149,15 @@ func (s *server) Shutdown() { log.Infof("Shutting down server") s.lock.Lock() defer s.lock.Unlock() - s.service.Shutdown() - s.tokenTapirService.Shutdown() - log.Infof("Closing Token server Database...") - s.tokenServer.Close() - s.metricsPack.Stop() - s.running = true - s.SetAttribute(AttrEnabled, "false") + if s.running { + s.service.Shutdown() + s.tokenTapirService.Shutdown() + log.Infof("Closing Token server Database...") + s.tokenServer.Close() + s.metricsPack.Stop() + s.running = false + s.SetAttribute(AttrEnabled, "false") + } } // Statistics is an encapsulation of information about the server that an operator might want to know at a glance. @@ -194,11 +193,13 @@ func (s *server) Onion() string { return s.config.Onion() } -func (s *server) Server() string { +// ServerBundle returns a bundle of the server keys required to access it (torv3 keys are addresses) +func (s *server) ServerBundle() string { bundle := s.KeyBundle().Serialize() return fmt.Sprintf("server:%s", base64.StdEncoding.EncodeToString(bundle)) } +// TofuBundle returns a Server Bundle + a newly created group invite func (s *server) TofuBundle() string { group, _ := model.NewGroup(tor.GetTorV3Hostname(s.config.PublicKey)) invite, err := group.Invite() @@ -209,21 +210,6 @@ func (s *server) TofuBundle() string { return fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite) } -// TODO demo implementation only, not nearly enough entropy -// TODO Apache license -// https://github.com/dustinkirkland/petname/blob/master/usr/share/petname/small/names.txt -var namesSmall = []string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "haddock", "hagfish", "halibut", "hamster", "herring", "jackass", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "woodcock"} - -func (s *server) HashName() string { - var bytes []byte = s.config.PublicKey - var words []string - for i := 0; i < 8; i++ { - index := int(binary.BigEndian.Uint32(bytes[i*4:(i+1)*4])) % len(namesSmall) - words = append(words, namesSmall[index]) - } - return strings.Join(words, "-") -} - // GetAttribute gets a server attribute func (s *server) GetAttribute(key string) string { return s.config.GetAttribute(key) diff --git a/serverConfig.go b/serverConfig.go index 3f4f5cd..a7087d3 100644 --- a/serverConfig.go +++ b/serverConfig.go @@ -27,6 +27,20 @@ const ( // AttrEnabled is the attribute key for user toggle of server being enabled AttrEnabled = "enabled" + + // AttrStorageType is used by clients that may need info about stored server config types/styles + AttrStorageType = "storageType" +) + +const ( + // StorageTypeDefaultPassword is a AttrStorageType that indicated a app default password was used + StorageTypeDefaultPassword = "storage-default-password" + + // StorageTypePassword is a AttrStorageType that indicated a user password was used to protect the profile + StorageTypePassword = "storage-password" + + // StoreageTypeNoPassword is a AttrStorageType that indicated a no password was used to protect the profile + StoreageTypeNoPassword = "storage-no-password" ) // Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report @@ -54,8 +68,10 @@ type Config struct { ServerReporting Reporting `json:"serverReporting"` - attributes map[string]string - lock sync.Mutex + Attributes map[string]string `json:"attributes"` + + lock sync.Mutex + encFileStore v1.FileStore } // Identity returns an encapsulation of the servers keys @@ -69,7 +85,7 @@ func (config *Config) TokenServiceIdentity() primitives.Identity { } func initDefaultConfig(configDir, filename string, encrypted bool) *Config { - config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename} + config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename, Attributes: make(map[string]string)} id, pk := primitives.InitializeEphemeralIdentity() tid, tpk := primitives.InitializeEphemeralIdentity() @@ -83,8 +99,8 @@ func initDefaultConfig(configDir, filename string, encrypted bool) *Config { ReportingGroupID: "", ReportingServerAddr: "", } - config.attributes[AttrAutostart] = "false" - config.attributes[AttrEnabled] = "true" + config.Attributes[AttrAutostart] = "false" + config.Attributes[AttrEnabled] = "true" k := new(ristretto255.Scalar) b := make([]byte, 64) @@ -110,6 +126,7 @@ func LoadCreateDefaultConfigFile(configDir, filename string, encrypted bool, pas // CreateConfig creates a default config and saves it to a json file specified by filename // if the encrypted flag is true the config is store encrypted by password func CreateConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { + log.Debugf("CreateConfig for server with configDir: %s\n", configDir) os.Mkdir(configDir, 0700) config := initDefaultConfig(configDir, filename, encrypted) if encrypted { @@ -119,6 +136,7 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( return nil, err } config.key = key + config.encFileStore = v1.NewFileStore(configDir, ServerConfigFile, key) } config.Save() @@ -127,23 +145,23 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( // LoadConfig loads a Config from a json file specified by filename func LoadConfig(configDir, filename string, encrypted bool, password string) (*Config, error) { - log.Infof("Loading config from %s\n", path.Join(configDir, filename)) - config := initDefaultConfig(configDir, filename, encrypted) - - raw, err := ioutil.ReadFile(path.Join(configDir, filename)) - if err != nil { - return nil, err - } - + var raw []byte + var err error if encrypted { salt, err := ioutil.ReadFile(path.Join(configDir, SaltFile)) if err != nil { return nil, err } key := v1.CreateKey(password, salt) - settingsStore := v1.NewFileStore(configDir, ServerConfigFile, key) - raw, err = settingsStore.Read() + config.encFileStore = v1.NewFileStore(configDir, ServerConfigFile, key) + raw, err = config.encFileStore.Read() + if err != nil { + log.Errorf("read enc bytes failed: %s\n", err) + return nil, err + } + } else { + raw, err = ioutil.ReadFile(path.Join(configDir, filename)) if err != nil { return nil, err } @@ -161,17 +179,20 @@ func LoadConfig(configDir, filename string, encrypted bool, password string) (*C // Save dumps the latest version of the config to a json file given by filename func (config *Config) Save() error { - log.Infof("Saving config to %s\n", path.Join(config.ConfigDir, config.FilePath)) + config.lock.Lock() + defer config.lock.Unlock() bytes, _ := json.MarshalIndent(config, "", "\t") if config.Encrypted { - settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) - return settingStore.Write(bytes) + //settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) + return config.encFileStore.Write(bytes) } return ioutil.WriteFile(path.Join(config.ConfigDir, config.FilePath), bytes, 0600) } // CheckPassword returns true if the given password produces the same key as the current stored key, otherwise false. func (config *Config) CheckPassword(checkpass string) bool { + config.lock.Lock() + defer config.lock.Unlock() salt, err := ioutil.ReadFile(path.Join(config.ConfigDir, SaltFile)) if err != nil { return false @@ -182,14 +203,16 @@ func (config *Config) CheckPassword(checkpass string) bool { // Onion returns the .onion url for the server func (config *Config) Onion() string { + config.lock.Lock() + defer config.lock.Unlock() return tor.GetTorV3Hostname(config.PublicKey) + ".onion" } // SetAttribute sets a server attribute func (config *Config) SetAttribute(key, val string) { config.lock.Lock() - defer config.lock.Unlock() - config.attributes[key] = val + config.Attributes[key] = val + config.lock.Unlock() config.Save() } @@ -197,5 +220,5 @@ func (config *Config) SetAttribute(key, val string) { func (config *Config) GetAttribute(key string) string { config.lock.Lock() defer config.lock.Unlock() - return config.attributes[key] + return config.Attributes[key] } diff --git a/servers.go b/servers.go index f8e4f74..bf619e4 100644 --- a/servers.go +++ b/servers.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "git.openprivacy.ca/openprivacy/connectivity" + "git.openprivacy.ca/openprivacy/log" "io/ioutil" "path" "sync" @@ -37,7 +38,7 @@ type servers struct { // NewServers returns a Servers interface to manage a collection of servers // expecting directory: $CWTCH_HOME/servers func NewServers(acn connectivity.ACN, directory string) Servers { - return &servers{acn: acn, directory: directory} + return &servers{acn: acn, directory: directory, servers: make(map[string]Server)} } // LoadServers will attempt to load any servers in the servers directory that are encrypted with the supplied password @@ -50,16 +51,19 @@ func (s *servers) LoadServers(password string) ([]string, error) { if err != nil { return nil, fmt.Errorf("error: cannot read server directory: %v", err) } - loadedServers := []string{} for _, dir := range dirs { newConfig, err := LoadConfig(path.Join(s.directory, dir.Name()), ServerConfigFile, true, password) - if _, exists := s.servers[newConfig.Onion()]; err == nil && !exists { - server := NewServer(newConfig) - s.servers[server.Onion()] = server - loadedServers = append(loadedServers, server.Onion()) + if err == nil { + if _, exists := s.servers[newConfig.Onion()]; !exists { + log.Debugf("Loaded config, building server for %s\n", newConfig.Onion()) + server := NewServer(newConfig) + s.servers[server.Onion()] = server + loadedServers = append(loadedServers, server.Onion()) + } } } + log.Infof("LoadServers returning: %s\n", loadedServers) return loadedServers, nil } From 44da3fd95fbbfbc3f2d3e048fd0e7ec52a6490be Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 29 Oct 2021 16:06:18 -0700 Subject: [PATCH 4/6] change -exportTofuBundle to -exportServerBundle --- README.md | 2 +- app/main.go | 6 +++--- docker/Dockerfile | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 62f0f2c..ff4d5fe 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The app takes the following arguments - -debug: enabled debug logging -- -exportTofuBundle: Export the tofubundle to a file called tofubundle +- -exportServerBundle: Export the server bundle to a file called serverbundle The app takes the following environment variables diff --git a/app/main.go b/app/main.go index 45c814e..e24982c 100644 --- a/app/main.go +++ b/app/main.go @@ -19,7 +19,7 @@ import ( func main() { flagDebug := flag.Bool("debug", false, "Enable debug logging") - flagExportTofu := flag.Bool("exportTofuBundle", false, "Export the tofubundle to a file called tofubundle") + flagExportServer := flag.Bool("exportServerBundle", false, "Export the server bundle to a file called serverbundle") flag.Parse() log.AddEverythingFromPattern("server/app/main") @@ -85,9 +85,9 @@ func main() { log.Infof("Server bundle (import into client to use server): %s\n", log.Magenta(server.ServerBundle())) - if *flagExportTofu { + if *flagExportServer { // Todo: change all to server export - ioutil.WriteFile(path.Join(serverConfig.ConfigDir, "tofubundle"), []byte(server.TofuBundle()), 0600) + ioutil.WriteFile(path.Join(serverConfig.ConfigDir, "serverbundle"), []byte(server.TofuBundle()), 0600) } // Graceful Shutdown diff --git a/docker/Dockerfile b/docker/Dockerfile index 6fb3a9e..52268c5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -64,5 +64,5 @@ COPY ./docker/docker-entrypoint /usr/local/bin/ VOLUME /etc/tor /var/lib/tor /var/lib/cwtch ENTRYPOINT ["docker-entrypoint"] -CMD ["/usr/local/bin/cwtch","--exportTofuBundle"] +CMD ["/usr/local/bin/cwtch","--exportServerBundle"] From 88746426017e0b6674f9aec5c1a70fb437583c40 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 29 Oct 2021 17:46:34 -0700 Subject: [PATCH 5/6] addressing PR concerns + adding servers test --- go.mod | 6 ++--- go.sum | 2 ++ server.go | 9 ++++---- serverConfig.go | 3 +-- servers.go | 7 +++--- servers_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ testing/tests.sh | 1 + 7 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 servers_test.go diff --git a/go.mod b/go.mod index 52cc76b..e48b5e6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.openprivacy.ca/cwtch.im/server go 1.14 require ( - cwtch.im/cwtch v0.8.5 + cwtch.im/cwtch v0.12.2 git.openprivacy.ca/cwtch.im/tapir v0.4.9 git.openprivacy.ca/openprivacy/connectivity v1.5.0 git.openprivacy.ca/openprivacy/log v1.0.3 @@ -11,6 +11,4 @@ require ( github.com/mattn/go-sqlite3 v1.14.7 github.com/struCoder/pidusage v0.2.1 golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee -) - -replace cwtch.im/cwtch => /home/dan/src/go/src/cwtch.im/cwtch +) \ No newline at end of file diff --git a/go.sum b/go.sum index de82a84..a6605ec 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cwtch.im/cwtch v0.8.0 h1:QDRaDBTXefFRPZPqUMtxoNhOcgXv0rl0bGjysSOmJX0= cwtch.im/cwtch v0.8.0/go.mod h1:+SY/4ueF1U7mK+CX8hZFbtd+GC1lx/cReo110KgtQAw= cwtch.im/cwtch v0.8.5 h1:W67jAF2oRwqWytbZEv1UeCqW0cU2x69tgUw8iy27xFA= cwtch.im/cwtch v0.8.5/go.mod h1:5GHxaaeVnKeXSU64IvtCKzkqhU8DRiLoVM+tiBT8kkc= +cwtch.im/cwtch v0.12.2 h1:I+ndKadCRCITw4SPbd+1cpRv+z/7iHjjTUv8OzRwTrE= +cwtch.im/cwtch v0.12.2/go.mod h1:QpTkQK7MqNt0dQK9/pBk5VpkvFhy6xuoxJIn401B8fM= filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.openprivacy.ca/cwtch.im/tapir v0.4.0 h1:clG8uORt0NKEhT4P+Dpw1pzyUuYzYBMevGqn2pciKk8= diff --git a/server.go b/server.go index 1239b9c..9fd9e5d 100644 --- a/server.go +++ b/server.go @@ -63,10 +63,6 @@ func NewServer(serverConfig *Config) Server { server := new(server) server.running = false server.config = serverConfig - bs := new(persistence.BoltPersistence) - bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db")) - server.tokenServer = privacypass.NewTokenServerFromStore(&serverConfig.TokenServiceK, bs) - log.Infof("Y: %v", server.tokenServer.Y) server.tokenService = server.config.TokenServiceIdentity() server.tokenServicePrivKey = server.config.TokenServerPrivateKey return server @@ -85,6 +81,11 @@ func (s *server) Run(acn connectivity.ACN) error { return nil } + bs := new(persistence.BoltPersistence) + bs.Open(path.Join(s.config.ConfigDir, "tokens.db")) + s.tokenServer = privacypass.NewTokenServerFromStore(&s.config.TokenServiceK, bs) + log.Infof("Y: %v", s.tokenServer.Y) + identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey) var service tapir.Service service = new(tor2.BaseOnionService) diff --git a/serverConfig.go b/serverConfig.go index a7087d3..8fb9e43 100644 --- a/serverConfig.go +++ b/serverConfig.go @@ -132,7 +132,7 @@ func CreateConfig(configDir, filename string, encrypted bool, password string) ( if encrypted { key, _, err := v1.InitV1Directory(configDir, password) if err != nil { - log.Errorf("Could not create server directory: %s", err) + log.Errorf("could not create server directory: %s", err) return nil, err } config.key = key @@ -183,7 +183,6 @@ func (config *Config) Save() error { defer config.lock.Unlock() bytes, _ := json.MarshalIndent(config, "", "\t") if config.Encrypted { - //settingStore := v1.NewFileStore(config.ConfigDir, config.FilePath, config.key) return config.encFileStore.Write(bytes) } return ioutil.WriteFile(path.Join(config.ConfigDir, config.FilePath), bytes, 0600) diff --git a/servers.go b/servers.go index bf619e4..27995a4 100644 --- a/servers.go +++ b/servers.go @@ -46,7 +46,6 @@ func NewServers(acn connectivity.ACN, directory string) Servers { func (s *servers) LoadServers(password string) ([]string, error) { s.lock.Lock() defer s.lock.Unlock() - dirs, err := ioutil.ReadDir(s.directory) if err != nil { return nil, fmt.Errorf("error: cannot read server directory: %v", err) @@ -56,7 +55,7 @@ func (s *servers) LoadServers(password string) ([]string, error) { newConfig, err := LoadConfig(path.Join(s.directory, dir.Name()), ServerConfigFile, true, password) if err == nil { if _, exists := s.servers[newConfig.Onion()]; !exists { - log.Debugf("Loaded config, building server for %s\n", newConfig.Onion()) + log.Debugf("Loaded config, building server for %s\n", newConfig.Onion()) server := NewServer(newConfig) s.servers[server.Onion()] = server loadedServers = append(loadedServers, server.Onion()) @@ -111,7 +110,7 @@ func (s *servers) DeleteServer(onion string, password string) error { delete(s.servers, onion) return err } - return errors.New("Server not found") + return errors.New("server not found") } // LaunchServer Run() the specified server @@ -125,6 +124,8 @@ func (s *servers) LaunchServer(onion string) { // ShutdownServer Shutsdown the specified server func (s *servers) ShutdownServer(onion string) { + s.lock.Lock() + defer s.lock.Unlock() s.servers[onion].Shutdown() } diff --git a/servers_test.go b/servers_test.go new file mode 100644 index 0000000..8e5c7c2 --- /dev/null +++ b/servers_test.go @@ -0,0 +1,59 @@ +package server + +import ( + "git.openprivacy.ca/openprivacy/connectivity" + "git.openprivacy.ca/openprivacy/log" + "os" + "testing" +) + +const TestDir = "./serversTest" +const DefaultPassword = "be gay do crime" + +const TestServerDesc = "a test Server" + +func TestServers(t *testing.T) { + log.SetLevel(log.LevelDebug) + log.Infof("clean up / setup...\n") + os.RemoveAll(TestDir) + os.Mkdir(TestDir, 0700) + + acn := connectivity.NewLocalACN() + log.Infof("NewServers()...\n") + servers := NewServers(acn, TestDir) + s, err := servers.CreateServer(DefaultPassword) + if err != nil { + t.Errorf("could not create server: %s", err) + return + } + s.SetAttribute(AttrDescription, TestServerDesc) + serverOnion := s.Onion() + + s.Shutdown() + + log.Infof("NewServers()...\n" ) + servers2 := NewServers(acn, TestDir) + log.Infof("LoadServers()...\n") + list, err := servers2.LoadServers(DefaultPassword) + log.Infof("Loaded!\n") + if err != nil { + t.Errorf("clould not load server: %s", err) + return + } + if len(list) != 1 { + t.Errorf("expected to load 1 server, got %d", len(list)) + return + } + + if list[0] != serverOnion { + t.Errorf("expected loaded server to have onion: %s but got %s", serverOnion, list[0]) + } + + s1 := servers.GetServer(list[0]) + if s1.GetAttribute(AttrDescription) != TestServerDesc { + t.Errorf("expected server description of '%s' but got '%s'", TestServerDesc, s1.GetAttribute(AttrDescription)) + } + + servers2.Shutdown() + os.RemoveAll(TestDir) +} \ No newline at end of file diff --git a/testing/tests.sh b/testing/tests.sh index 962e34e..5c83d77 100755 --- a/testing/tests.sh +++ b/testing/tests.sh @@ -5,6 +5,7 @@ pwd GORACE="haltonerror=1" go test -race ${1} -coverprofile=server.metrics.cover.out -v ./metrics go test -race ${1} -coverprofile=server.metrics.cover.out -v ./storage +go test -race ${1} -coverprofile=server.metrics.cover.out -v ./ echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out rm -rf *.cover.out From 0f1d94d27e3a24b4a1e93e28dfae3dd1e5bb8436 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Mon, 1 Nov 2021 09:00:41 -0700 Subject: [PATCH 6/6] fix tests.sh --- testing/tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/tests.sh b/testing/tests.sh index 5c83d77..e2263fe 100755 --- a/testing/tests.sh +++ b/testing/tests.sh @@ -4,8 +4,8 @@ set -e pwd GORACE="haltonerror=1" go test -race ${1} -coverprofile=server.metrics.cover.out -v ./metrics -go test -race ${1} -coverprofile=server.metrics.cover.out -v ./storage -go test -race ${1} -coverprofile=server.metrics.cover.out -v ./ +go test -race ${1} -coverprofile=server.storage.cover.out -v ./storage +go test -race ${1} -coverprofile=server.cover.out -v ./ echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out rm -rf *.cover.out