Skip to content

Commit

Permalink
added manager node config and associated commands
Browse files Browse the repository at this point in the history
  • Loading branch information
林志宇 committed Mar 27, 2019
1 parent 466dbac commit 0cbba05
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 64 deletions.
68 changes: 68 additions & 0 deletions cmd/manager-node/commands/init-config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package commands

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/skycoin/skywire/pkg/manager"
)

const (
homeMode = "HOME"
localMode = "LOCAL"
)

var initConfigModes = []string{homeMode, localMode}

var (
output string
replace bool
mode string
)

func init() {
rootCmd.AddCommand(initConfigCmd)

initConfigCmd.Flags().StringVarP(&output, "output", "o", defaultConfigPaths[0], "path of output config file.")
initConfigCmd.Flags().BoolVarP(&replace, "replace", "r", false, "whether to allow rewrite of a file that already exists.")
initConfigCmd.Flags().StringVarP(&mode, "mode", "m", homeMode, fmt.Sprintf("config generation mode. Valid values: %v", initConfigModes))
}

var initConfigCmd = &cobra.Command{
Use: "init-config",
Short: "generates a configuration file",
Run: func(_ *cobra.Command, _ []string) {
output, err := filepath.Abs(output)
if err != nil {
log.WithError(err).Fatalln("invalid output provided")
}
var conf manager.Config
switch mode {
case homeMode:
conf = manager.GenerateHomeConfig()
case localMode:
conf = manager.GenerateLocalConfig()
default:
log.Fatalln("invalid mode:", mode)
}
raw, err := json.MarshalIndent(conf, "", " ")
if err != nil {
log.WithError(err).Fatal("unexpected error, report to dev")
}
if _, err := os.Stat(output); !replace && err == nil {
log.Fatalf("file %s already exists, stopping as 'replace,r' flag is not set", output)
}
if err := os.MkdirAll(filepath.Dir(output), 0750); err != nil {
log.WithError(err).Fatalln("failed to create output directory")
}
if err := ioutil.WriteFile(output, raw, 0744); err != nil {
log.WithError(err).Fatalln("failed to write file")
}
log.Infof("Wrote %d bytes to %s\n%s", len(raw), output, string(raw))
},
}
76 changes: 49 additions & 27 deletions cmd/manager-node/commands/root.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,85 @@
package commands

import (
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"

"github.com/skycoin/skycoin/src/util/logging"
"github.com/spf13/cobra"

"github.com/skycoin/skywire/pkg/cipher"
"github.com/skycoin/skywire/internal/pathutil"
"github.com/skycoin/skywire/pkg/manager"
)

var (
pk cipher.PubKey
sk cipher.SecKey
rpcAddr string
httpAddr string
log = logging.MustGetLogger("manager-node")

mock bool
mockNodes int
mockMaxTps int
mockMaxRoutes int

log = logging.MustGetLogger("manager-node")
defaultConfigPaths = [2]string{
filepath.Join(pathutil.HomeDir(), ".skycoin/skywire-manager/config.json"),
"/usr/local/skycoin/skywire-manager/config.json",
}
)

func init() {
rootCmd.PersistentFlags().Var(&pk, "pk", "manager node's public key")
rootCmd.PersistentFlags().Var(&sk, "sk", "manager node's secret key")
rootCmd.PersistentFlags().StringVar(&httpAddr, "http-addr", ":8080", "address to serve HTTP RESTful API and Web interface")
func findConfigPath() (string, error) {
log.Info("configuration file is not explicitly specified, attempting to find one in default paths ...")
for i, cPath := range defaultConfigPaths {
if _, err := os.Stat(cPath); err != nil {
log.Infof("- [%d/%d] '%s' does not exist", i, len(defaultConfigPaths), cPath)
} else {
log.Infof("- [%d/%d] '%s' exists (using this one)", i, len(defaultConfigPaths), cPath)
return cPath, nil
}
}
return "", errors.New("no configuration file found")
}

rootCmd.Flags().StringVar(&rpcAddr, "rpc-addr", ":7080", "address to serve RPC client interface")
func init() {
rootCmd.Flags().BoolVar(&mock, "mock", false, "whether to run manager node with mock data")
rootCmd.Flags().IntVar(&mockNodes, "mock-nodes", 5, "number of app nodes to have in mock mode")
rootCmd.Flags().IntVar(&mockMaxTps, "mock-max-tps", 10, "max number of transports per mock app node")
rootCmd.Flags().IntVar(&mockMaxRoutes, "mock-max-routes", 10, "max number of routes per node")
}

var rootCmd = &cobra.Command{
Use: "manager-node",
Use: "manager-node [config-path]",
Short: "Manages Skywire App Nodes",
PreRun: func(_ *cobra.Command, _ []string) {
if pk.Null() && sk.Null() {
pk, sk = cipher.GenerateKeyPair()
log.Println("No keys are set. Randomly generating...")
}
cPK, err := sk.PubKey()
if err != nil {
log.Fatalln("Key pair check failed:", err)
Run: func(_ *cobra.Command, args []string) {

var configPath string
if len(args) == 0 {
var err error
if configPath, err = findConfigPath(); err != nil {
log.WithError(err).Fatal()
}
} else {
configPath = args[0]
}
if cPK != pk {
log.Fatalln("SK and PK provided do not match.")
log.Infof("config path: '%s'", configPath)

var config manager.Config
config.FillDefaults()
if err := config.Parse(configPath); err != nil {
log.WithError(err).Fatalln("failed to parse config file")
}
log.Println("PK:", pk)
log.Println("SK:", sk)
},
Run: func(_ *cobra.Command, _ []string) {
m, err := manager.NewNode(manager.MakeConfig("manager.db")) // TODO: complete
var (
httpAddr = config.Interfaces.HTTPAddr
rpcAddr = config.Interfaces.RPCAddr
)

m, err := manager.NewNode(config)
if err != nil {
log.Fatalln("Failed to start manager:", err)
}

log.Infof("serving RPC on '%s'", rpcAddr)
go func() {
l, err := net.Listen("tcp", rpcAddr)
Expand All @@ -71,6 +90,7 @@ var rootCmd = &cobra.Command{
log.Fatalln("Failed to serve RPC:", err)
}
}()

if mock {
err := m.AddMockData(&manager.MockConfig{
Nodes: mockNodes,
Expand All @@ -81,10 +101,12 @@ var rootCmd = &cobra.Command{
log.Fatalln("Failed to add mock data:", err)
}
}

log.Infof("serving HTTP on '%s'", httpAddr)
if err := http.ListenAndServe(httpAddr, m); err != nil {
log.Fatalln("Manager exited with error:", err)
}

log.Println("Good bye!")
},
}
Expand Down
Binary file removed cmd/manager-node/manager.db
Binary file not shown.
19 changes: 19 additions & 0 deletions internal/pathutil/homedir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package pathutil

import (
"os"
"runtime"
)

// HomeDir obtains the path to the user's home directory via ENVs.
// SRC: https://github.com/spf13/viper/blob/80ab6657f9ec7e5761f6603320d3d58dfe6970f6/util.go#L144-L153
func HomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}
95 changes: 76 additions & 19 deletions pkg/manager/config.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,99 @@
package manager

import (
"encoding/hex"
"encoding/json"
"net/http"
"os"
"path/filepath"
"time"

"github.com/skycoin/skywire/internal/pathutil"

"github.com/skycoin/skywire/pkg/cipher"
)

type Key []byte

func (hk Key) String() string {
return hex.EncodeToString(hk)
}

func (hk Key) MarshalText() ([]byte, error) {
return []byte(hk.String()), nil
}

func (hk *Key) UnmarshalText(text []byte) error {
_, err := hex.Decode(*hk, text)
return err
}

type Config struct {
PK cipher.PubKey
SK cipher.SecKey
DBPath string
NamePattern string // regular expression for usernames (no check if empty). TODO
PassPattern string // regular expression for passwords (no check of empty). TODO
PassSaltLen int // Salt Len for password verification data.
Cookies CookieConfig
PK cipher.PubKey `json:"public_key"`
SK cipher.SecKey `json:"secret_key"`
DBPath string `json:"db_path"`
NameRegexp string `json:"username_regexp"` // regular expression for usernames (no check if empty). TODO
PassRegexp string `json:"password_regexp"` // regular expression for passwords (no check of empty). TODO
PassSaltLen int `json:"password_salt_len"` // Salt Len for password verification data.
Cookies CookieConfig `json:"cookies"`
Interfaces InterfaceConfig `json:"interfaces"`
}

func MakeConfig(dbPath string) Config {
func makeConfig() Config {
var c Config
pk, sk := cipher.GenerateKeyPair()
c.PK = pk
c.SK = sk
c.DBPath = dbPath
c.Cookies.HashKey = cipher.RandByte(64)
c.Cookies.BlockKey = cipher.RandByte(32)
c.FillDefaults()
return c
}

func GenerateHomeConfig() Config {
c := makeConfig()
c.DBPath = filepath.Join(pathutil.HomeDir(), ".skycoin/skywire-manager/users.db")
return c
}

func GenerateLocalConfig() Config {
c := makeConfig()
c.DBPath = "/usr/local/skycoin/skywire-manager/users.db"
return c
}

func (c *Config) FillDefaults() {
c.NamePattern = `^(admin)$`
c.PassPattern = `((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})`
c.NameRegexp = `^(admin)$`
c.PassRegexp = `((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})`
c.PassSaltLen = 16
c.Cookies.FillDefaults()
c.Interfaces.FillDefaults()
}

func (c *Config) Parse(path string) error {
var err error
if path, err = filepath.Abs(path); err != nil {
return err
}
f, err := os.Open(path)
if err != nil {
return err
}
defer func() { catch(f.Close()) }()
return json.NewDecoder(f).Decode(c)
}

type CookieConfig struct {
HashKey []byte // 32 or 64 bytes.
BlockKey []byte // 16 (AES-128), 24 (AES-192), 32 (AES-256) bytes. (optional)
HashKey Key `json:"hash_key"` // 32 or 64 bytes.
BlockKey Key `json:"block_key"` // 16 (AES-128), 24 (AES-192), 32 (AES-256) bytes. (optional)

ExpiresDuration time.Duration
ExpiresDuration time.Duration `json:"expires_duration"`

Path string // optional
Domain string // optional
Secure bool
HttpOnly bool
SameSite http.SameSite
Path string `json:"path"` // optional
Domain string `json:"domain"` // optional
Secure bool `json:"secure"`
HttpOnly bool `json:"http_only"`
SameSite http.SameSite `json:"same_site"`
}

func (c *CookieConfig) FillDefaults() {
Expand All @@ -56,3 +103,13 @@ func (c *CookieConfig) FillDefaults() {
c.HttpOnly = true
c.SameSite = http.SameSiteDefaultMode
}

type InterfaceConfig struct {
HTTPAddr string `json:"http_address"`
RPCAddr string `json:"rpc_addr"`
}

func (c *InterfaceConfig) FillDefaults() {
c.HTTPAddr = ":8080"
c.RPCAddr = ":7080"
}
Loading

0 comments on commit 0cbba05

Please sign in to comment.