Author | Jakob Wakeling <[email protected]> |
Date | 2023-11-27 10:52:28 |
Commit | b8047017f5dc43349e044cdcee1994c6e906ca47 |
Parent | c768e5c1b66cada5b99688924036c9a4f5d1a272 |
Add CSRF protection middleware
Diffstat
M | README.md | | | 4 | ++-- |
M | go.mod | | | 2 | ++ |
M | go.sum | | | 6 | ++++++ |
M | res/admin/repo_edit.html | | | 3 | +++ |
M | res/admin/user_create.html | | | 1 | + |
M | res/admin/user_edit.html | | | 1 | + |
M | res/repo/create.html | | | 1 | + |
M | res/repo/edit.html | | | 3 | +++ |
M | res/user/edit.html | | | 2 | ++ |
M | res/user/login.html | | | 1 | + |
M | src/admin/repos.go | | | 6 | ++++++ |
M | src/admin/users.go | | | 10 | ++++++++++ |
M | src/goit/auth.go | | | 6 | +++++- |
M | src/goit/goit.go | | | 2 | ++ |
M | src/main.go | | | 8 | +++++++- |
M | src/repo/create.go | | | 10 | +++++++++- |
M | src/repo/edit.go | | | 6 | ++++++ |
M | src/user/edit.go | | | 6 | ++++++ |
M | src/user/login.go | | | 10 | +++++++++- |
19 files changed, 82 insertions, 6 deletions
diff --git a/README.md b/README.md index 4291fd5..d205e4c 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ A simple and lightweight Git web server. - Git Smart HTTP protocol (v2 only) - Git SSH protocol (planned) - Repository log, tree, refs, and commit viewers -- File viewer with syntax highlighting (planned) -- File raw, blame, and history views (planned) +- File viewer with (planned) syntax highlighting +- File log, blame (planned), and raw views - Public and private repositories - Read and write permissions for non owners (planned) - Repository mirroring (pull and/or push) (planned) diff --git a/go.mod b/go.mod index 2baa3d1..3e82a89 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/buildkite/terminal-to-html/v3 v3.9.1 github.com/dustin/go-humanize v1.0.1 github.com/go-git/go-git/v5 v5.9.0 + github.com/gorilla/csrf v1.7.2 github.com/gorilla/mux v1.8.0 github.com/mattn/go-sqlite3 v1.14.17 golang.org/x/crypto v0.14.0 @@ -23,6 +24,7 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect diff --git a/go.sum b/go.sum index fcf75df..67e4c90 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,14 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= +github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/res/admin/repo_edit.html b/res/admin/repo_edit.html index 1b852a6..14243d0 100644 --- a/res/admin/repo_edit.html +++ b/res/admin/repo_edit.html @@ -5,6 +5,7 @@ <main> <h1>{{.Title}}</h1><hr> <form action="/admin/repo/edit?repo={{.Edit.Id}}" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="edit"> <table> <tr> @@ -48,6 +49,7 @@ <br><h2>Transfer Ownership</h2><hr> <span>- You will lose access to this repository if it is not public.</span><br><br> <form action="/admin/repo/edit?repo={{.Edit.Id}}" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="transfer"> <table> <tr><td><label for="owner">New Owner</label></td></tr> @@ -63,6 +65,7 @@ <span>- This operation <b>CANNOT</b> be undone.</span><br> <span>- This operation will permanently delete the {{.Name}} repository and all associated data.</span><br><br> <form action="/admin/repo/edit?repo={{.Edit.Id}}" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="delete"> <table> <tr><td><label for="reponame">To confirm, type "{{.Name}}" in the box below</label></td></tr> diff --git a/res/admin/user_create.html b/res/admin/user_create.html index 7909270..0352901 100644 --- a/res/admin/user_create.html +++ b/res/admin/user_create.html @@ -19,6 +19,7 @@ <main> <h1>{{.Title}}</h1><hr> <form action="/admin/user/create" method="post"> + {{.CsrfField}} <table> <tr><td><label for="username">Username</label></td></tr> <tr><td><input type="text" name="username" value="{{.Form.Name}}" spellcheck="false"></td></tr> diff --git a/res/admin/user_edit.html b/res/admin/user_edit.html index 4f4f73f..2696ddb 100644 --- a/res/admin/user_edit.html +++ b/res/admin/user_edit.html @@ -19,6 +19,7 @@ <main> <h1>{{.Title}}</h1><hr> <form action="/admin/user/edit?user={{.Form.Id}}" method="post"> + {{.CsrfField}} <table> <tr><td><label for="id">ID</label></td></tr> <tr><td><span>{{.Form.Id}}</span></td></tr> diff --git a/res/repo/create.html b/res/repo/create.html index 753e578..20c31b1 100644 --- a/res/repo/create.html +++ b/res/repo/create.html @@ -12,6 +12,7 @@ </header> <main> <form action="/repo/create" method="post"> + {{.CsrfField}} <table> <tr> <td style="text-align: right;"><label for="reponame">Name</label></td> diff --git a/res/repo/edit.html b/res/repo/edit.html index 768c864..6f99739 100644 --- a/res/repo/edit.html +++ b/res/repo/edit.html @@ -5,6 +5,7 @@ <main> <h1>{{.Title}}</h1><hr> <form action="/{{.Name}}/edit" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="edit"> <table> <tr> @@ -48,6 +49,7 @@ <br><h2>Transfer Ownership</h2><hr> <span>- You will lose access to this repository if it is not public.</span><br><br> <form action="/{{.Name}}/edit" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="transfer"> <table> <tr><td><label for="owner">New Owner</label></td></tr> @@ -63,6 +65,7 @@ <span>- This operation <b>CANNOT</b> be undone.</span><br> <span>- This operation will permanently delete the {{.Name}} repository and all associated data.</span><br><br> <form action="/{{.Name}}/edit" method="post"> + {{.CsrfField}} <input type="hidden" name="action" value="delete"> <table> <tr><td><label for="reponame">To confirm, type "{{.Name}}" in the box below</label></td></tr> diff --git a/res/user/edit.html b/res/user/edit.html index 2f8ee56..5317316 100644 --- a/res/user/edit.html +++ b/res/user/edit.html @@ -5,6 +5,7 @@ <main> <h1>{{.Title}}</h1><hr> <form action="/user/edit" method="post"> + {{.CsrfField}} <table> <tr><td><label for="username">Username</label></td></tr> <tr><td><input type="text" name="username" value="{{.Form.Name}}" spellcheck="false"></td></tr> @@ -21,6 +22,7 @@ </table> </form><hr> <form action="/user/edit" method="post"> + {{.CsrfField}} <table> <tr><td><label for="password">Current Password</label></td></tr> <tr><td><input type="password" name="password"></td></tr> diff --git a/res/user/login.html b/res/user/login.html index 4d235cf..6bbc819 100644 --- a/res/user/login.html +++ b/res/user/login.html @@ -12,6 +12,7 @@ </header> <main> <form action="/user/login" method="post"> + {{.CsrfField}} <table> <tr> <td style="text-align: right;"><label for="username">Username</label></td> diff --git a/src/admin/repos.go b/src/admin/repos.go index 01d1d66..fdf7598 100644 --- a/src/admin/repos.go +++ b/src/admin/repos.go @@ -2,6 +2,7 @@ package admin import ( "fmt" + "html/template" "log" "net/http" "slices" @@ -10,6 +11,7 @@ import ( "github.com/Jamozed/Goit/src/goit" "github.com/Jamozed/Goit/src/util" "github.com/dustin/go-humanize" + "github.com/gorilla/csrf" ) func HandleRepos(w http.ResponseWriter, r *http.Request) { @@ -110,9 +112,13 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) { Transfer struct{ Owner, Message string } Delete struct{ Message string } + + CsrfField template.HTML }{ Title: "Admin - Edit Repository", Name: repo.Name, + + CsrfField: csrf.TemplateField(r), } data.Edit.Id = fmt.Sprint(repo.Id) diff --git a/src/admin/users.go b/src/admin/users.go index edf1edd..5d1a674 100644 --- a/src/admin/users.go +++ b/src/admin/users.go @@ -2,6 +2,7 @@ package admin import ( "fmt" + "html/template" "log" "net/http" "slices" @@ -10,6 +11,7 @@ import ( "github.com/Jamozed/Goit/src/goit" "github.com/Jamozed/Goit/src/util" + "github.com/gorilla/csrf" ) func HandleUsers(w http.ResponseWriter, r *http.Request) { @@ -69,8 +71,12 @@ func HandleUserCreate(w http.ResponseWriter, r *http.Request) { Name, FullName string IsAdmin bool } + + CsrfField template.HTML }{ Title: "Admin - Create User", + + CsrfField: csrf.TemplateField(r), } if r.Method == http.MethodPost { @@ -148,8 +154,12 @@ func HandleUserEdit(w http.ResponseWriter, r *http.Request) { Id, Name, FullName string IsAdmin bool } + + CsrfField template.HTML }{ Title: "Admin - Edit User", + + CsrfField: csrf.TemplateField(r), } data.Form.Id = fmt.Sprint(u.Id) diff --git a/src/goit/auth.go b/src/goit/auth.go index 108f1f2..809cf18 100644 --- a/src/goit/auth.go +++ b/src/goit/auth.go @@ -111,7 +111,11 @@ func CleanupSessions() { /* Set a user session cookie. */ func SetSessionCookie(w http.ResponseWriter, uid int64, s Session) { - c := &http.Cookie{Name: "session", Value: fmt.Sprint(uid) + "." + s.Token, Path: "/", Expires: s.Expiry} + c := &http.Cookie{ + Name: "session", Value: fmt.Sprint(uid) + "." + s.Token, Path: "/", Expires: s.Expiry, + Secure: util.If(Conf.UsesHttps, true, false), HttpOnly: true, SameSite: http.SameSiteLaxMode, + } + if err := c.Valid(); err != nil { log.Println("[Cookie]", err.Error()) } diff --git a/src/goit/goit.go b/src/goit/goit.go index 25130bd..59051bf 100644 --- a/src/goit/goit.go +++ b/src/goit/goit.go @@ -33,6 +33,7 @@ type Config struct { GitPath string `json:"git_path"` IpSessions bool `json:"ip_sessions"` UsesHttps bool `json:"uses_https"` + CsrfSecret string `json:"csrf_secret"` } var Conf = Config{ @@ -42,6 +43,7 @@ var Conf = Config{ GitPath: "git", IpSessions: true, UsesHttps: false, + CsrfSecret: "1234567890abcdef1234567890abcdef", } var db *sql.DB diff --git a/src/main.go b/src/main.go index d5cdf8c..26bf003 100644 --- a/src/main.go +++ b/src/main.go @@ -25,6 +25,7 @@ import ( "github.com/Jamozed/Goit/src/user" "github.com/Jamozed/Goit/src/util" "github.com/adrg/xdg" + "github.com/gorilla/csrf" "github.com/gorilla/mux" ) @@ -136,8 +137,13 @@ func main() { wait.Add(1) go handleIpc(stop, wait, ipc) + protect := csrf.Protect( + []byte(goit.Conf.CsrfSecret), csrf.FieldName("csrf.Token"), csrf.CookieName("csrf"), + csrf.Secure(util.If(goit.Conf.UsesHttps, true, false)), + ) + /* Listen for HTTP on the specified port */ - if err := http.ListenAndServe(goit.Conf.HttpAddr+":"+goit.Conf.HttpPort, logHttp(h)); err != nil { + if err := http.ListenAndServe(goit.Conf.HttpAddr+":"+goit.Conf.HttpPort, protect(logHttp(h))); err != nil { log.Fatalln("[HTTP]", err.Error()) } } diff --git a/src/repo/create.go b/src/repo/create.go index af6131c..48f5a3f 100644 --- a/src/repo/create.go +++ b/src/repo/create.go @@ -1,11 +1,13 @@ package repo import ( + "html/template" "log" "net/http" "slices" "github.com/Jamozed/Goit/src/goit" + "github.com/gorilla/csrf" ) func HandleCreate(w http.ResponseWriter, r *http.Request) { @@ -19,7 +21,13 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) { Title, Message string Name, Description string IsPrivate bool - }{Title: "Repository - Create"} + + CsrfField template.HTML + }{ + Title: "Repository - Create", + + CsrfField: csrf.TemplateField(r), + } if r.Method == http.MethodPost { data.Name = r.FormValue("reponame") diff --git a/src/repo/edit.go b/src/repo/edit.go index eb98841..fb24573 100644 --- a/src/repo/edit.go +++ b/src/repo/edit.go @@ -3,6 +3,7 @@ package repo import ( "errors" "fmt" + "html/template" "log" "net/http" "path/filepath" @@ -12,6 +13,7 @@ import ( "github.com/Jamozed/Goit/src/util" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/gorilla/csrf" "github.com/gorilla/mux" ) @@ -62,12 +64,16 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { Transfer struct{ Owner, Message string } Delete struct{ Message string } + + CsrfField template.HTML }{ Title: "Repository - Edit", Name: repo.Name, Description: repo.Description, Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, Editable: (auth && repo.OwnerId == user.Id), + + CsrfField: csrf.TemplateField(r), } data.Edit.Id = fmt.Sprint(repo.Id) diff --git a/src/user/edit.go b/src/user/edit.go index 7ef2739..19cda31 100644 --- a/src/user/edit.go +++ b/src/user/edit.go @@ -3,11 +3,13 @@ package user import ( "bytes" "fmt" + "html/template" "log" "net/http" "slices" "github.com/Jamozed/Goit/src/goit" + "github.com/gorilla/csrf" ) func HandleEdit(w http.ResponseWriter, r *http.Request) { @@ -32,8 +34,12 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { Title, MessageA, MessageB string Form struct{ Id, Name, FullName string } + + CsrfField template.HTML }{ Title: "User - Edit", + + CsrfField: csrf.TemplateField(r), } data.Form.Id = fmt.Sprint(user.Id) diff --git a/src/user/login.go b/src/user/login.go index 2d031f6..a87e7fa 100644 --- a/src/user/login.go +++ b/src/user/login.go @@ -6,12 +6,14 @@ package user import ( "bytes" + "html/template" "log" "net" "net/http" "time" "github.com/Jamozed/Goit/src/goit" + "github.com/gorilla/csrf" ) func HandleLogin(w http.ResponseWriter, r *http.Request) { @@ -22,7 +24,13 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) { data := struct { Title, Message, Name string FocusPw bool - }{Title: "Login"} + + CsrfField template.HTML + }{ + Title: "Login", + + CsrfField: csrf.TemplateField(r), + } if r.Method == http.MethodPost { data.Name = r.FormValue("username")