diff --git a/.gitignore b/.gitignore index 3a2321a..2c66faf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ tmp/ *.db *.db-shm *.db-wal + +# certs +*.pem \ No newline at end of file diff --git a/Makefile b/Makefile index f50ca24..155ef29 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ PHONEY: serve # SQLite Commands - sql-cli: sqlite3 $(SQL_DATABASE) -cmd ".headers on" -cmd ".mode box" -cmd ".tables" @@ -29,4 +28,9 @@ check-system-deps: @command -v air > /dev/null || (echo "Missing air command. go install github.com/air-verse/air@latest"; exit 1) @command -v sqlite3 > /dev/null || (echo "Missing sqlite3 command. brew install sqlite"; exit 1) @command -v migrate > /dev/null || (echo "Missing migrate command. go install -tags 'sqlite3' github.com/golang-migrate/migrate/v4/cmd/migrate@latest"; exit 1) - @echo "System dependencies fulfilled πŸ‘" \ No newline at end of file + @echo "System dependencies fulfilled πŸ‘" + +# Certs + +local-certs: + cd ./tls && go run /usr/local/go/src/crypto/tls/generate_cert.go --rsa-bits=2048 --host=localhost diff --git a/README.md b/README.md index 26b9db8..64ef9ad 100644 --- a/README.md +++ b/README.md @@ -246,4 +246,8 @@ It has: - Expires/ Max-Age for roughly 3 days - httpOnly: true - Secure: true -- SameSite: None \ No newline at end of file +- SameSite: None + +## Local TLS Certs + +https://github.com/FiloSottile/mkcert Can be used to create local certs with a trusted CA installed on the machine. \ No newline at end of file diff --git a/cmd/ratchetd/main.go b/cmd/ratchetd/main.go index f170a06..cfcf2db 100644 --- a/cmd/ratchetd/main.go +++ b/cmd/ratchetd/main.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "flag" "fmt" "log/slog" @@ -11,7 +12,6 @@ import ( rdb "git.runcible.io/learning/ratchet/internal/database" "git.runcible.io/learning/ratchet/internal/logging" "git.runcible.io/learning/ratchet/internal/server" - "github.com/alexedwards/scs/sqlite3store" "github.com/alexedwards/scs/v2" _ "github.com/mattn/go-sqlite3" // "git.runcible.io/learning/ratchet" @@ -30,6 +30,8 @@ func main() { port := flag.String("port", "5001", "HTTP port") logLevel := flag.String("logging", "INFO", "Logging Level. Valid values [INFO, DEBUG, WARN, ERROR].") dbPath := flag.String("database", "./ratchet.db", "A path to a sqlite3 database") + certPath := flag.String("cert", "./tls/cert.pem", "A public cert in .pem format") + keyPath := flag.String("key", "./tls/key.pem", "A private key in .pem format") // must call parse or all values will be the defaults flag.Parse() @@ -54,19 +56,72 @@ func main() { // SessionManager sm := scs.New() - sm.Store = sqlite3store.New(db) - sm.Lifetime = 12 * time.Hour - // Propagate build information to root package to share globally // ratchet.Version = strings.TrimPrefix(version, "") // ratchet.Commit = commit - server := server.NewRatchetServer(logger, tc, db, sm) + app := server.NewRatchetApp(logger, tc, db, sm) + + // these two elliptic curves have assembly implementations + tlsConfig := tls.Config{ + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, + // example of cipher suites usage. These are ignored if TLS 1.3 is used + // CipherSuites: []uint16{ + // tls.TLS_AES_128_GCM_SHA256, + // tls.TLS_AES_256_GCM_SHA384, + // tls.TLS_CHACHA20_POLY1305_SHA256, + // tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + // tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + // tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + // tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + // tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + // tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + // }, + // example of explicitly setting TLS versions supported. + // you do this for older browsers or for devices like micros that + // have hardware limitations. + // MinVersion: tls.VersionTLS10, + // MaxVersion: tls.VersionTLS12, + } // START SERVING REQUESTS slog.Debug("Herp dirp!") slog.Info(fmt.Sprintf("Listening on http://%s:%s", *addr, *port)) //log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", *addr, *port), server)) // there is no log.Fatal equivalent. This is an approximation of the behavior - err = http.ListenAndServe(fmt.Sprintf("%s:%s", *addr, *port), server) + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%s", *addr, *port), + Handler: app, + ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), + TLSConfig: &tlsConfig, + // Server wide timeouts. + // IdleTimeout by default go uses keep-alives on accepted connetions. + // this lowers the default timeout. If you set read timeout but not idle + // then the idle becomes the readtimeout. Be careful. + IdleTimeout: time.Minute, + // If the request headers or body are still being read 5 seconds after the + // request is first accepted, then Go will close the underlying connection + // Care close. User won't recieve any http response + // See also http.Server also provides a ReadHeaderTimeout setting. Would allow + // per route timeouts. + ReadTimeout: 5 * time.Second, + // The WriteTimeout setting will close the underlying connection if our server + // attempts to write to the connection after a given period. When using TLS + // it's sensible to set WriteTimeout above ReadTimeout + + // It’s important to bear in mind that writes made by a handler are buffered and + // written to the connection as one when the handler returns. Therefore, the + // idea of WriteTimeout is generally not to prevent long-running handlers, but + // to prevent the data that the handler returns from taking too long to write. + WriteTimeout: 10 * time.Second, + // the maximum number of bytes the server will read when parsing request headers. + // default is one mb. Exceeding this returns a 431 Request Header Fields Too Large response + // btw go adds 4096 bytes to the number you provide. + MaxHeaderBytes: 524288, // 0.5 mb + } + + slog.Info(fmt.Sprintf("Listening on https://%s", srv.Addr)) + + err = srv.ListenAndServeTLS(*certPath, *keyPath) slog.Error(err.Error()) os.Exit(1) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 97c86dd..098f365 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -129,7 +129,7 @@ func handleSnippetView(logger *slog.Logger, tc *TemplateCache, sm *scs.SessionMa // } data := newTemplateData(r, sm) data.Snippet = snippet - data.Flash = flash + // data.Flash = flash renderTemplate(w, r, tc, http.StatusOK, "view.go.tmpl", data) }) } diff --git a/internal/server/server.go b/internal/server/server.go index 749d861..a7828d9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -10,7 +10,7 @@ import ( "github.com/go-playground/form/v4" ) -type RatchetServer struct { +type RatchetApp struct { http.Handler logger *slog.Logger @@ -22,8 +22,8 @@ type RatchetServer struct { sessionManager *scs.SessionManager } -func NewRatchetServer(logger *slog.Logger, tc *TemplateCache, db *sql.DB, sm *scs.SessionManager) *RatchetServer { - rs := new(RatchetServer) +func NewRatchetApp(logger *slog.Logger, tc *TemplateCache, db *sql.DB, sm *scs.SessionManager) *RatchetApp { + rs := new(RatchetApp) rs.logger = logger rs.snippetService = &model.SnippetService{DB: db} rs.formDecoder = form.NewDecoder() diff --git a/internal/server/templates.go b/internal/server/templates.go index 8b3928a..52ab009 100644 --- a/internal/server/templates.go +++ b/internal/server/templates.go @@ -133,7 +133,7 @@ func renderTemplate(w http.ResponseWriter, r *http.Request, tc *TemplateCache, s serverError(w, r, err) return } - + w.Header().Set("Content-Length", "this isn't an integer!") w.WriteHeader(status) buf.WriteTo(w) diff --git a/tls/.gitkeep b/tls/.gitkeep new file mode 100644 index 0000000..e69de29