From 73ea8b8ee4c07b5072f7b563c0995eb1f8f8c0ba Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Mon, 7 Mar 2016 17:37:39 -0700 Subject: [PATCH] authentication resolves #3 --- cmd/tile38-cli/main.go | 4 + cmd/tile38-server/main.go | 54 +++++++++--- controller/auth.go | 15 ++++ controller/config.go | 162 ++++++++++++++++++++++++++++++++++++ controller/controller.go | 96 ++++++++++----------- controller/follow.go | 2 +- controller/readonly.go | 2 +- controller/server/server.go | 52 ++++++++++-- core/commands.go | 75 ++++++++++++++--- core/commands.json | 64 ++++++++++++-- core/commands_template.go | 11 ++- core/options.go | 2 + core/version.go | 6 +- 13 files changed, 450 insertions(+), 95 deletions(-) create mode 100644 controller/auth.go create mode 100644 controller/config.go diff --git a/cmd/tile38-cli/main.go b/cmd/tile38-cli/main.go index 1f0e892a..2d34ac8f 100644 --- a/cmd/tile38-cli/main.go +++ b/cmd/tile38-cli/main.go @@ -113,6 +113,10 @@ func main() { if !parseArgs() { return } + if len(oneCommand) > 0 && (oneCommand[0] == 'h' || oneCommand[0] == 'H') && strings.Split(strings.ToLower(oneCommand), " ")[0] == "help" { + showHelp() + return + } addr := fmt.Sprintf("%s:%d", hostname, port) conn, err := client.Dial(addr) diff --git a/cmd/tile38-server/main.go b/cmd/tile38-server/main.go index ed32b578..74b0bf55 100644 --- a/cmd/tile38-server/main.go +++ b/cmd/tile38-server/main.go @@ -8,6 +8,7 @@ import ( "os" "runtime" "strconv" + "strings" "github.com/tidwall/tile38/controller" "github.com/tidwall/tile38/controller/log" @@ -15,23 +16,48 @@ import ( ) var ( - dir string - port int - host string - verbose bool - veryVerbose bool - devMode bool - quiet bool + dir string + port int + host string + verbose bool + veryVerbose bool + devMode bool + quiet bool + protectedMode bool = true ) func main() { + // parse non standard args. + nargs := []string{os.Args[0]} + for i := 1; i < len(os.Args); i++ { + switch os.Args[i] { + case "--protected-mode", "-protected-mode": + i++ + if i < len(os.Args) { + switch strings.ToLower(os.Args[i]) { + case "no": + protectedMode = false + case "yes": + protectedMode = true + } + continue + } + fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n") + os.Exit(1) + case "--dev", "-dev": + devMode = true + continue + } + nargs = append(nargs, os.Args[i]) + } + os.Args = nargs + flag.IntVar(&port, "p", 9851, "The listening port.") - flag.StringVar(&host, "h", "127.0.0.1", "The listening host.") + flag.StringVar(&host, "h", "", "The listening host.") flag.StringVar(&dir, "d", "data", "The data directory.") flag.BoolVar(&verbose, "v", false, "Enable verbose logging.") flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.") flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.") - flag.BoolVar(&devMode, "dev", false, "Activates dev mode. DEV ONLY.") flag.Parse() var logw io.Writer = os.Stderr if quiet { @@ -43,6 +69,12 @@ func main() { }) core.DevMode = devMode core.ShowDebugMessages = veryVerbose + core.ProtectedMode = protectedMode + + hostd := "" + if host != "" { + hostd = "Addr: " + host + ", " + } // _____ _ _ ___ ___ // |_ _|_| |___|_ | . | @@ -53,11 +85,11 @@ func main() { _______ _______ | | | |____ | _ | Tile38 %s (%s) %d bit (%s/%s) - | | | Host: %s, Port: %d, PID: %d + | | | %sPort: %d, PID: %d |____ | _ | | | | tile38.com |_______|_______| -`+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, host, port, os.Getpid()) +`+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd, port, os.Getpid()) if err := controller.ListenAndServe(host, port, dir); err != nil { log.Fatal(err) diff --git a/controller/auth.go b/controller/auth.go new file mode 100644 index 00000000..a8660e7b --- /dev/null +++ b/controller/auth.go @@ -0,0 +1,15 @@ +package controller + +func (c *Controller) cmdAuth(line string) error { + var password string + if line, password = token(line); password == "" { + return errInvalidNumberOfArguments + } + if line != "" { + return errInvalidNumberOfArguments + } + + println(password) + + return nil +} diff --git a/controller/config.go b/controller/config.go new file mode 100644 index 00000000..2e1f6e9e --- /dev/null +++ b/controller/config.go @@ -0,0 +1,162 @@ +package controller + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + "time" +) + +// Config is a tile38 config +type Config struct { + FollowHost string `json:"follow_host,omitempty"` + FollowPort int `json:"follow_port,omitempty"` + FollowID string `json:"follow_id,omitempty"` + FollowPos int `json:"follow_pos,omitempty"` + ServerID string `json:"server_id,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + + // Properties + RequirePassP string `json:"requirepass,omitempty"` + RequirePass string `json:"-"` + LeaderAuthP string `json:"leaderauth,omitempty"` + LeaderAuth string `json:"-"` + ProtectedModeP string `json:"protected-mode,omitempty"` + ProtectedMode string `json:"-"` +} + +func (c *Controller) loadConfig() error { + data, err := ioutil.ReadFile(c.dir + "/config") + if err != nil { + if os.IsNotExist(err) { + return c.initConfig() + } + return err + } + err = json.Unmarshal(data, &c.config) + if err != nil { + return err + } + // load properties + if err := c.setConfigProperty("requirepass", c.config.RequirePassP, true); err != nil { + return err + } + if err := c.setConfigProperty("leaderauth", c.config.LeaderAuthP, true); err != nil { + return err + } + if err := c.setConfigProperty("protected-mode", c.config.ProtectedModeP, true); err != nil { + return err + } + return nil +} + +func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error { + var invalid bool + switch name { + default: + return fmt.Errorf("Unsupported CONFIG parameter: %s", name) + case "requirepass": + c.config.RequirePass = value + case "leaderauth": + c.config.LeaderAuth = value + case "protected-mode": + switch strings.ToLower(value) { + case "": + if fromLoad { + c.config.ProtectedMode = "yes" + } else { + invalid = true + } + case "yes", "no": + c.config.ProtectedMode = strings.ToLower(value) + default: + invalid = true + } + } + if invalid { + return fmt.Errorf("Invalid argument '%s' for CONFIG SET '%s'", value, name) + } + return nil +} + +func (c *Controller) getConfigProperty(name string) string { + switch name { + default: + return "" + case "requirepass": + return c.config.RequirePass + case "leaderauth": + return c.config.LeaderAuth + case "protected-mode": + return c.config.ProtectedMode + } +} + +func (c *Controller) initConfig() error { + c.config = Config{ServerID: randomKey(16)} + return c.writeConfig(true) +} + +func (c *Controller) writeConfig(writeProperties bool) error { + var err error + bak := c.config + defer func() { + if err != nil { + // revert changes + c.config = bak + } + }() + if writeProperties { + // save properties + c.config.RequirePassP = c.config.RequirePass + c.config.LeaderAuthP = c.config.LeaderAuth + c.config.ProtectedModeP = c.config.ProtectedMode + } + var data []byte + data, err = json.MarshalIndent(c.config, "", "\t") + if err != nil { + return err + } + err = ioutil.WriteFile(c.dir+"/config", data, 0600) + if err != nil { + return err + } + return nil +} + +func (c *Controller) cmdConfig(line string) (string, error) { + var start = time.Now() + var cmd, name, value string + if line, cmd = token(line); cmd == "" { + return "", errInvalidNumberOfArguments + } + var buf bytes.Buffer + buf.WriteString(`{"ok":true`) + switch strings.ToLower(cmd) { + default: + return "", errInvalidArgument(cmd) + case "get": + if line, name = token(line); name == "" || line != "" { + return "", errInvalidNumberOfArguments + } + value = c.getConfigProperty(name) + buf.WriteString(`,"value":` + jsonString(value)) + case "set": + if line, name = token(line); name == "" { + return "", errInvalidNumberOfArguments + } + value = strings.TrimSpace(line) + if err := c.setConfigProperty(name, value, false); err != nil { + return "", err + } + case "rewrite": + if err := c.writeConfig(true); err != nil { + return "", err + } + } + buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") + return buf.String(), nil +} diff --git a/controller/controller.go b/controller/controller.go index cb6dc2c4..bf76181f 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -4,14 +4,12 @@ import ( "bufio" "bytes" "crypto/rand" - "encoding/json" "errors" "fmt" "io" - "io/ioutil" - "net" "os" "runtime" + "strings" "sync" "time" @@ -64,16 +62,6 @@ type Controller struct { shrinking bool // aof shrinking flag } -// Config is a tile38 config -type Config struct { - FollowHost string `json:"follow_host,omitempty"` - FollowPort int `json:"follow_port,omitempty"` - FollowID string `json:"follow_id,omitempty"` - FollowPos int `json:"follow_pos,omitempty"` - ServerID string `json:"server_id,omitempty"` - ReadOnly bool `json:"read_only,omitempty"` -} - // ListenAndServe starts a new tile38 server func ListenAndServe(host string, port int, dir string) error { log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA) @@ -113,8 +101,8 @@ func ListenAndServe(host string, port int, dir string) error { c.mu.Unlock() }() go c.processLives() - handler := func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error { - err := c.handleInputCommand(string(command), w) + handler := func(conn *server.Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error { + err := c.handleInputCommand(conn, string(command), w) if err != nil { if err.Error() == "going live" { return c.goLive(err, conn, rd, websocket) @@ -123,7 +111,21 @@ func ListenAndServe(host string, port int, dir string) error { } return nil } - return server.ListenAndServe(host, port, handler) + protected := func() bool { + if !core.ProtectedMode { + // --protected-mode no + return false + } + if host != "" && host != "127.0.0.1" && host != "::1" && host != "localhost" { + // -h address + return false + } + c.mu.RLock() + is := c.config.ProtectedMode != "no" && c.config.RequirePass == "" + c.mu.RUnlock() + return is + } + return server.ListenAndServe(host, port, protected, handler) } func (c *Controller) setCol(key string, col *collection.Collection) { @@ -156,12 +158,12 @@ func isReservedFieldName(field string) bool { return false } -func (c *Controller) handleInputCommand(line string, w io.Writer) error { +func (c *Controller) handleInputCommand(conn *server.Conn, line string, w io.Writer) error { if core.ShowDebugMessages && line != "pInG" { log.Debug(line) } start := time.Now() - // Ping and Help. Just send back the response. No need to put through the pipeline. + // Ping. Just send back the response. No need to put through the pipeline. if len(line) == 4 && (line[0] == 'p' || line[0] == 'P') && lc(line, "ping") { w.Write([]byte(`{"ok":true,"ping":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`)) return nil @@ -180,6 +182,26 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error { if cmd == "" { return writeErr(errors.New("empty command")) } + + if !conn.Authenticated { + c.mu.RLock() + requirePass := c.config.RequirePass + c.mu.RUnlock() + if requirePass != "" { + // This better be an AUTH command. + if cmd != "auth" { + // Just shut down the pipeline now. The less the client connection knows the better. + return writeErr(errors.New("authentication required")) + } + password, _ := token(line) + if requirePass == strings.TrimSpace(password) { + conn.Authenticated = true + } else { + return writeErr(errors.New("invalid password")) + } + } + } + // choose the locking strategy switch cmd { default: @@ -203,7 +225,7 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error { if c.config.FollowHost != "" && !c.fcup { return writeErr(errors.New("catching up to leader")) } - case "follow", "readonly": + case "follow", "readonly", "config": // system operations // does not write to aof, but requires a write lock. c.mu.Lock() @@ -235,37 +257,6 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error { return nil } -func (c *Controller) loadConfig() error { - data, err := ioutil.ReadFile(c.dir + "/config") - if err != nil { - if os.IsNotExist(err) { - return c.initConfig() - } - return err - } - err = json.Unmarshal(data, &c.config) - if err != nil { - return err - } - return nil -} - -func (c *Controller) initConfig() error { - c.config = Config{ServerID: randomKey(16)} - return c.writeConfig() -} - -func (c *Controller) writeConfig() error { - data, err := json.MarshalIndent(c.config, "", "\t") - if err != nil { - return err - } - if err := ioutil.WriteFile(c.dir+"/config", data, 0600); err != nil { - return err - } - return nil -} - func randomKey(n int) string { b := make([]byte, n) nn, err := rand.Read(b) @@ -322,9 +313,14 @@ func (c *Controller) command(line string, w io.Writer) (resp string, d commandDe case "follow": err = c.cmdFollow(nline) resp = okResp() + case "config": + resp, err = c.cmdConfig(nline) case "readonly": err = c.cmdReadOnly(nline) resp = okResp() + case "auth": + err = c.cmdAuth(nline) + resp = okResp() case "stats": resp, err = c.cmdServer(nline) case "server": diff --git a/controller/follow.go b/controller/follow.go index 77d8c0ce..a6efcbf2 100644 --- a/controller/follow.go +++ b/controller/follow.go @@ -71,7 +71,7 @@ func (c *Controller) cmdFollow(line string) error { c.config.FollowHost = host c.config.FollowPort = port } - if err := c.writeConfig(); err != nil { + if err := c.writeConfig(false); err != nil { c.config = pconfig // revert return err } diff --git a/controller/readonly.go b/controller/readonly.go index c51e75ce..9f98a9ac 100644 --- a/controller/readonly.go +++ b/controller/readonly.go @@ -31,7 +31,7 @@ func (c *Controller) cmdReadOnly(line string) error { c.config.ReadOnly = false log.Info("read write") } - err := c.writeConfig() + err := c.writeConfig(false) if err != nil { c.config = backup return err diff --git a/controller/server/server.go b/controller/server/server.go index 38b2ff29..6621d283 100644 --- a/controller/server/server.go +++ b/controller/server/server.go @@ -14,12 +14,44 @@ import ( "github.com/tidwall/tile38/core" ) +// This phrase is copied nearly verbatim from Redis. +// https://github.com/antirez/redis/blob/cf42c48adcea05c1bd4b939fcd36a01f23ec6303/src/networking.c +var deniedMessage = []byte(strings.TrimSpace(` +ACCESS DENIED +Tile38 is running in protected mode because protected mode is enabled, no host +address was specified, no authentication password is requested to clients. In +this mode connections are only accepted from the loopback interface. If you +want to connect from external computers to Tile38 you may adopt one of the +following solutions: + +1) Disable protected mode sending the command 'CONFIG SET protected-mode no' + from the loopback interface by connecting to Tile38 from the same host + the server is running, however MAKE SURE Tile38 is not publicly accessible + from internet if you do so. Use CONFIG REWRITE to make this change + permanent. +2) Alternatively you can just disable the protected mode by editing the Tile38 + configuration file, and setting the protected mode option to 'no', and then + restarting the server. +3) If you started the server manually just for testing, restart it with the + '--protected-mode no' option. +4) Setup a host address or an authentication password. + +NOTE: You only need to do one of the above things in order for the server +to start accepting connections from the outside. +`) + "\r\n") + +type Conn struct { + net.Conn + Authenticated bool +} + var errCloseHTTP = errors.New("close http") // ListenAndServe starts a tile38 server at the specified address. func ListenAndServe( host string, port int, - handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error, + protected func() bool, + handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error, ) error { ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { @@ -32,13 +64,14 @@ func ListenAndServe( log.Error(err) continue } - go handleConn(conn, handler) + go handleConn(&Conn{Conn: conn}, protected, handler) } } func handleConn( - conn net.Conn, - handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error, + conn *Conn, + protected func() bool, + handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error, ) { if core.ShowDebugMessages { addr := conn.RemoteAddr().String() @@ -46,6 +79,14 @@ func handleConn( defer func() { log.Debugf("closed connection: %s", addr) }() + if !strings.HasPrefix(addr, "127.0.0.1:") && !strings.HasPrefix(addr, "[::1]:") { + if protected() { + // This is a protected server. Only loopback is allowed. + conn.Write(deniedMessage) + conn.Close() + return + } + } } defer conn.Close() rd := bufio.NewReader(conn) @@ -59,7 +100,8 @@ func handleConn( return io.EOF } var b bytes.Buffer - if err := handler(command, conn, rd, &b, proto == client.WebSocket); err != nil { + + if err := handler(conn, command, rd, &b, proto == client.WebSocket); err != nil { if proto == client.HTTP { conn.Write([]byte(`HTTP/1.1 500 ` + err.Error() + "\r\nConnection: close\r\n\r\n")) } diff --git a/core/commands.go b/core/commands.go index bdcdbb0d..4cb31654 100644 --- a/core/commands.go +++ b/core/commands.go @@ -33,13 +33,13 @@ func (c Command) String() string { func (c Command) TermOutput(indent string) string { line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear line2 := yellow + "summary: " + clear + c.Summary - line3 := yellow + "since: " + clear + c.Since - return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n" + //line3 := yellow + "since: " + clear + c.Since + return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n" } type EnumArg struct { Name string `json:"name"` - Arguments []Argument `json:"arguments` + Arguments []Argument `json:"arguments"` } func (a EnumArg) String() string { @@ -846,12 +846,43 @@ var commandsJSON = `{ "since": "1.0.0", "group": "search" }, - "PING": { - "summary":"Ping the server", - "complexity": "O(1)", - "arguments": [], - "since": "1.0.0", - "group": "server" + "CONFIG GET": { + "summary": "Authenticate to the server", + "arguments": [ + { + "name": "which", + "enumargs": [ + { + "name": "GET", + "arguments":[ + { + "name": "parameter", + "type": "string" + } + ] + }, + { + "name": "SET", + "arguments":[ + { + "name": "parameter", + "type": "string" + }, + { + "name": "value", + "type": "string", + "optional": true + } + ] + }, + { + "name": "REWRITE", + "arguments":[] + } + ] + } + ], + "group": "connection" }, "SERVER": { "summary":"Show server stats and details", @@ -931,10 +962,25 @@ var commandsJSON = `{ }, "AOFSHRINK": { "summary": "Shrinks the aof in the background", - "complexity": "O(1)", - "arguments": [], - "since": "1.0.0", "group": "replication" + }, + "PING": { + "summary": "Ping the server", + "group": "connection" + }, + "QUIT": { + "summary": "Close the connection", + "group": "connection" + }, + "AUTH": { + "summary": "Authenticate to the server", + "arguments": [ + { + "name": "password", + "type": "string" + } + ], + "group": "connection" } }` @@ -960,6 +1006,8 @@ var commandsJSON = `{ // HELP -- Prints this menu. -- O(1) -- F() // READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean) // FLUSHDB -- Removes all keys. -- O(1) -- F() +// CONFIG SET property value -- Set a config property. Is not yet permanent. +// CONFIG REWRITE -- Make config changes permanent. // --- Replication --- // FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer) @@ -990,3 +1038,6 @@ var commandsJSON = `{ // QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey) // TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double) // GET... GET key id -- An internal object. -- F(key string, id string) + +// --- Security --- +// AUTH password -- Authenticate to server. diff --git a/core/commands.json b/core/commands.json index 1431b8b4..ed011c41 100644 --- a/core/commands.json +++ b/core/commands.json @@ -701,12 +701,43 @@ "since": "1.0.0", "group": "search" }, - "PING": { - "summary":"Ping the server", - "complexity": "O(1)", - "arguments": [], - "since": "1.0.0", - "group": "server" + "CONFIG": { + "summary": "Authenticate to the server", + "arguments": [ + { + "name": "which", + "enumargs": [ + { + "name": "GET", + "arguments":[ + { + "name": "parameter", + "type": "string" + } + ] + }, + { + "name": "SET", + "arguments":[ + { + "name": "parameter", + "type": "string" + }, + { + "name": "value", + "type": "string", + "optional": true + } + ] + }, + { + "name": "REWRITE", + "arguments":[] + } + ] + } + ], + "group": "connection" }, "SERVER": { "summary":"Show server stats and details", @@ -786,9 +817,24 @@ }, "AOFSHRINK": { "summary": "Shrinks the aof in the background", - "complexity": "O(1)", - "arguments": [], - "since": "1.0.0", "group": "replication" + }, + "PING": { + "summary": "Ping the server", + "group": "connection" + }, + "QUIT": { + "summary": "Close the connection", + "group": "connection" + }, + "AUTH": { + "summary": "Authenticate to the server", + "arguments": [ + { + "name": "password", + "type": "string" + } + ], + "group": "connection" } } \ No newline at end of file diff --git a/core/commands_template.go b/core/commands_template.go index 12de1e81..375c24ec 100644 --- a/core/commands_template.go +++ b/core/commands_template.go @@ -35,13 +35,13 @@ func (c Command) String() string { func (c Command) TermOutput(indent string) string { line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear line2 := yellow + "summary: " + clear + c.Summary - line3 := yellow + "since: " + clear + c.Since - return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n" + //line3 := yellow + "since: " + clear + c.Since + return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n" } type EnumArg struct { Name string `json:"name"` - Arguments []Argument `json:"arguments` + Arguments []Argument `json:"arguments"` } func (a EnumArg) String() string { @@ -169,6 +169,8 @@ var commandsJSON = `{{.CommandsJSON}}` // HELP -- Prints this menu. -- O(1) -- F() // READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean) // FLUSHDB -- Removes all keys. -- O(1) -- F() +// CONFIG SET property value -- Set a config property. Is not yet permanent. +// CONFIG REWRITE -- Make config changes permanent. // --- Replication --- // FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer) @@ -199,3 +201,6 @@ var commandsJSON = `{{.CommandsJSON}}` // QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey) // TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double) // GET... GET key id -- An internal object. -- F(key string, id string) + +// --- Security --- +// AUTH password -- Authenticate to server. diff --git a/core/options.go b/core/options.go index eaab1bf6..b1f31620 100644 --- a/core/options.go +++ b/core/options.go @@ -5,3 +5,5 @@ var DevMode = false // ShowDebugMessages allows for log.Debug to print to console. var ShowDebugMessages = false + +var ProtectedMode = true diff --git a/core/version.go b/core/version.go index 2dbe8bd0..1a85e39e 100644 --- a/core/version.go +++ b/core/version.go @@ -1,7 +1,7 @@ package core var ( - Version string - BuildTime string - GitSHA string + Version = "0.0.0" + BuildTime = "" + GitSHA = "0000000" )