sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

commit 17e376bca14fc1b052285bf996572cf5734b5e76
parent a5028d431073a0af539260308139f5f9e188d8d7
Author: Vetle Haflan <vetle@haflan.dev>
Date:   Sun, 12 Apr 2020 02:14:03 +0200

DB refactoring and start using securecookie

Diffstat:
M.gitignore | 1+
Mcmd/sermoni/main.go | 8+++-----
Minternal/database/database.go | 93++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Minternal/http/http.go | 2+-
Mui/generate.sh | 14+++++++++-----
5 files changed, 68 insertions(+), 50 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,3 +2,4 @@ sermoni.db **/test.db node_modules/ html.go +dist/ diff --git a/cmd/sermoni/main.go b/cmd/sermoni/main.go @@ -2,12 +2,12 @@ package main import ( "flag" - "fmt" "log" "sermoni/internal/database" smhttp "sermoni/internal/http" ) +// Flags var ( port = flag.Int("p", 8080, "Port") dbFile = flag.String("d", "sermoni.db", "Database file") @@ -17,10 +17,8 @@ func main() { // TODO: Use getopt package instead of flags? //password := flag.String("w", "", "Password for the web interface") flag.Parse() + database.Init(*dbFile) defer database.Close() - if err := database.Init(*dbFile); err != nil { - log.Fatal(err) - } - fmt.Printf("Server running on port %v\n", *port) + log.Printf("Server started listening on port %v\n", *port) smhttp.StartServer(*port) } diff --git a/internal/database/database.go b/internal/database/database.go @@ -9,6 +9,7 @@ import ( "golang.org/x/crypto/bcrypt" + "github.com/gorilla/securecookie" "go.etcd.io/bbolt" ) @@ -24,8 +25,11 @@ var ( BucketKeyServices = []byte("services") // bucket key for services bucket BucketKeyServiceTokens = []byte("service-tokens") // bucket key for service-tokens bucket - keyPassHash = []byte("passhash") - keyPageTitle = []byte("pagetitle") + keyPassHash = []byte("passhash") + keyPageTitle = []byte("pagetitle") + keySCHashKey = []byte("schashkey") // Secure cookie hash key + keySCBlockKey = []byte("blockkey") // Secure cookie block key + keyCSRFKey = []byte("csrfkey") // CSRF protection auth key ) // ErrConfigBucket is returned when bbolt is unable to open the config bucket @@ -34,47 +38,46 @@ var ErrConfigBucket = errors.New("unable to open config bucket") var db *bbolt.DB +// check for fatal errors +func check(err error) { + if err != nil { + log.Fatal(err) + } +} + // Init opens the database for the given file name or creates it if it doesn't exist. // It also populates it with essential configuration data if required. func Init(dbFileName string) error { - fmt.Printf("Init db '%v'\n", dbFileName) + log.Printf("Init db '%v'\n", dbFileName) var err error db, err = bbolt.Open(dbFileName, 0600, &bbolt.Options{Timeout: 1 * time.Second}) - if err != nil { - return err - } + check(err) // Create the necessary bbolt buckets if they don't exist - err = db.Update(func(tx *bbolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(BucketKeyConfig); err != nil { - return err - } - if _, err := tx.CreateBucketIfNotExists(BucketKeyServices); err != nil { - return err - } - if _, err := tx.CreateBucketIfNotExists(BucketKeyEvents); err != nil { - return err - } - if _, err := tx.CreateBucketIfNotExists(BucketKeyServiceTokens); err != nil { - return err - } + db.Update(func(tx *bbolt.Tx) error { + _, err = tx.CreateBucketIfNotExists(BucketKeyConfig) + check(err) + _, err = tx.CreateBucketIfNotExists(BucketKeyServices) + check(err) + _, err = tx.CreateBucketIfNotExists(BucketKeyEvents) + check(err) + _, err = tx.CreateBucketIfNotExists(BucketKeyServiceTokens) + check(err) return nil }) - if err != nil { - return err - } // Check if the config is initialized - configure if not var configured bool - err = db.View(func(tx *bbolt.Tx) error { + db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(BucketKeyConfig) if b == nil { - return ErrConfigBucket + panic(ErrConfigBucket) } passhash := b.Get(keyPassHash) configured = passhash != nil return nil }) if !configured { - return Reconfigure(defaultPassPhrase, defaultPageTitle) + initAuthKeys() + Reconfigure(defaultPassPhrase, defaultPageTitle) } return nil } @@ -82,7 +85,7 @@ func Init(dbFileName string) error { // Reconfigure takes a passphrase and a page title for the web page, // generates hash for the password and updates the database with this // new configuration. -func Reconfigure(passphrase string, pageTitle string) error { +func Reconfigure(passphrase string, pageTitle string) { // TODO: Maybe this belongs elsewhere? /* TODO: Generate a random _readable_ password if none is given var passphraseBytes []byte @@ -98,22 +101,36 @@ func Reconfigure(passphrase string, pageTitle string) error { // TODO: Maybe bcrypt is overkill for such a small project? Consider later passhash, err := bcrypt.GenerateFromPassword([]byte(passphrase), bcrypt.DefaultCost) - if err != nil { - return err - } - - return db.Update(func(tx *bbolt.Tx) error { + check(err) + db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(BucketKeyConfig) if b == nil { return ErrConfigBucket } var err error - if err = b.Put(keyPassHash, passhash); err != nil { - return err - } - if err = b.Put(keyPageTitle, []byte(pageTitle)); err != nil { - return err + err = b.Put(keyPassHash, passhash) + check(err) + err = b.Put(keyPageTitle, []byte(pageTitle)) + check(err) + return nil + }) +} + +// initAuthKeys generates session and CSRF protection authentitication keys +// and persists them to DB +func initAuthKeys() { + hashKey := securecookie.GenerateRandomKey(32) + blockKey := securecookie.GenerateRandomKey(32) + db.Update(func(tx *bbolt.Tx) error { + var err error + b := tx.Bucket(BucketKeyConfig) + if b == nil { + panic("the config bucket does not exist") } + err = b.Put(keySCHashKey, hashKey) + check(err) + err = b.Put(keySCBlockKey, blockKey) + check(err) return nil }) } @@ -135,9 +152,7 @@ func GetDB() *bbolt.DB { // If the parsing fails, the function therefore panics func BytesToUint64(byteData []byte) uint64 { uint64Data, err := strconv.ParseUint(string(byteData), 10, 64) - if err != nil { - log.Panic("couldn't parse byte data to uint64") - } + check(err) return uint64Data } diff --git a/internal/http/http.go b/internal/http/http.go @@ -14,6 +14,6 @@ func StartServer(port int) { func staticHandler(res http.ResponseWriter, r *http.Request) { defer r.Body.Close() - res.Write(websiteHTML) + res.Write(getWebsite()) return } diff --git a/ui/generate.sh b/ui/generate.sh @@ -1,7 +1,10 @@ #!/bin/sh +INDEX_HTML=dist/index.html +HTML_GO=dist/html.go + # index.html for development -cat <<EOF > index.html +cat <<EOF > $INDEX_HTML <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> @@ -16,12 +19,13 @@ $(cat ./dist/sermoni.js) EOF # html.go for production, ` must be replaced by `+"`"+` -cp index.html html.go -sed -i 's/`/`\+"`"\+`/g' html.go -cat <<EOF > html.go +cp $INDEX_HTML $HTML_GO +sed -i 's/`/`\+"`"\+`/g' $HTML_GO +cat <<EOF > $HTML_GO +// +build PRODUCTION package http var websiteHTML = []byte(\` -$(cat html.go) +$(cat $HTML_GO) \`) EOF